Skip to main content

capmonstercloud_client/requests/
basilisk.rs

1use serde::Serialize;
2
3use super::{require_non_empty, CaptchaRequest};
4use crate::error::Result;
5use crate::proxy::ProxyInfo;
6use crate::timeouts::GetResultTimeouts;
7
8/// Basilisk captcha (`CustomTask`, class `Basilisk`).
9#[derive(Debug, Clone, Serialize)]
10pub struct BasiliskCustomTaskRequest {
11    /// Address of the main page where the captcha is solved.
12    #[serde(rename = "websiteURL")]
13    pub website_url: String,
14    /// Site key: the `data-sitekey` attribute of the captcha container, or the `site_key` field of
15    /// the POST payload to `https://basiliskcaptcha.com/challenge/check-site`.
16    #[serde(rename = "websiteKey")]
17    pub website_key: String,
18    #[serde(flatten, skip_serializing_if = "Option::is_none")]
19    pub proxy: Option<ProxyInfo>,
20    /// Browser User-Agent (current Windows UA supported by CapMonster Cloud).
21    #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
22    pub user_agent: Option<String>,
23}
24
25impl BasiliskCustomTaskRequest {
26    pub fn new(website_url: impl Into<String>, website_key: impl Into<String>) -> Self {
27        Self {
28            website_url: website_url.into(),
29            website_key: website_key.into(),
30            proxy: None,
31            user_agent: None,
32        }
33    }
34
35    pub fn with_proxy(mut self, proxy: ProxyInfo) -> Self {
36        self.proxy = Some(proxy);
37        self
38    }
39
40    pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
41        self.user_agent = Some(value.into());
42        self
43    }
44}
45
46impl CaptchaRequest for BasiliskCustomTaskRequest {
47    const TASK_TYPE: &'static str = "CustomTask";
48    const CLASS: Option<&'static str> = Some("Basilisk");
49
50    fn timeouts(&self) -> GetResultTimeouts {
51        GetResultTimeouts::secs(1.0, 10.0, 3.0, 100.0)
52    }
53
54    fn validate(&self) -> Result<()> {
55        require_non_empty("websiteURL", &self.website_url)?;
56        require_non_empty("websiteKey", &self.website_key)?;
57        Ok(())
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::proxy::ProxyType;
65    use serde_json::json;
66
67    #[test]
68    fn minimal_task() {
69        let task = BasiliskCustomTaskRequest::new("https://example.com", "KEY")
70            .to_task()
71            .unwrap();
72        assert_eq!(
73            task,
74            json!({"type": "CustomTask", "class": "Basilisk", "websiteURL": "https://example.com", "websiteKey": "KEY"})
75        );
76    }
77
78    #[test]
79    fn full_task() {
80        let task = BasiliskCustomTaskRequest::new("https://example.com", "KEY")
81            .with_proxy(ProxyInfo::new(ProxyType::Https, "h.example", 3128).with_auth("u", "p"))
82            .with_user_agent("UA")
83            .to_task()
84            .unwrap();
85        assert_eq!(
86            task,
87            json!({
88                "type": "CustomTask", "class": "Basilisk", "websiteURL": "https://example.com", "websiteKey": "KEY",
89                "proxyType": "https", "proxyAddress": "h.example", "proxyPort": 3128,
90                "proxyLogin": "u", "proxyPassword": "p", "userAgent": "UA"
91            })
92        );
93    }
94
95    #[test]
96    fn empty_required_strings_are_rejected() {
97        let err = BasiliskCustomTaskRequest::new("", "KEY")
98            .to_task()
99            .unwrap_err();
100        assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
101        let err = BasiliskCustomTaskRequest::new("https://example.com", "")
102            .to_task()
103            .unwrap_err();
104        assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteKey")));
105    }
106}