Skip to main content

capmonstercloud_client/requests/
datadome.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/// DataDome solving method version (`datadomeVersion`).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
10pub enum DataDomeVersion {
11    /// Updated solving method supporting both `i.js` and `c.js` scripts (sent as `"new"`).
12    #[serde(rename = "new")]
13    New,
14}
15
16/// DataDome-specific challenge data. All values are dynamic: extract them right before
17/// creating the task.
18#[derive(Debug, Clone, Serialize)]
19pub struct DataDomeMetadata {
20    /// CAPTCHA URL with the latest dynamic parameters, usually
21    /// `https://geo.captcha-delivery.com/captcha/?initialCid=...&hash=...&cid=...&t=fe&referer=...&s=...`.
22    /// Submit it only if its `t` parameter is `fe` (`t=bv` means the proxy was blocked).
23    #[serde(rename = "captchaUrl")]
24    pub captcha_url: String,
25    /// Your DataDome cookies (`datadome=...`).
26    #[serde(rename = "datadomeCookie")]
27    pub datadome_cookie: String,
28    /// Solving method version. When absent the legacy method (only `i.js`) is used.
29    #[serde(rename = "datadomeVersion", skip_serializing_if = "Option::is_none")]
30    pub datadome_version: Option<DataDomeVersion>,
31}
32
33impl DataDomeMetadata {
34    pub fn new(captcha_url: impl Into<String>, datadome_cookie: impl Into<String>) -> Self {
35        Self {
36            captcha_url: captcha_url.into(),
37            datadome_cookie: datadome_cookie.into(),
38            datadome_version: None,
39        }
40    }
41
42    pub fn with_datadome_version(mut self, value: DataDomeVersion) -> Self {
43        self.datadome_version = Some(value);
44        self
45    }
46}
47
48/// DataDome captcha (`CustomTask`, class `DataDome`). Your own proxy is required.
49/// If the proxy uses IP authorization, whitelist `65.21.190.34`. The solution contains
50/// cookies that must be set in the browser.
51#[derive(Debug, Clone, Serialize)]
52pub struct DataDomeCustomTaskRequest {
53    /// The main page URL where the captcha is solved.
54    #[serde(rename = "websiteURL")]
55    pub website_url: String,
56    pub metadata: DataDomeMetadata,
57    /// Required proxy (non-transparent, not local).
58    #[serde(flatten)]
59    pub proxy: ProxyInfo,
60    /// Browser User-Agent (current Windows UA supported by CapMonster Cloud).
61    #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
62    pub user_agent: Option<String>,
63}
64
65impl DataDomeCustomTaskRequest {
66    pub fn new(
67        website_url: impl Into<String>,
68        metadata: DataDomeMetadata,
69        proxy: ProxyInfo,
70    ) -> Self {
71        Self {
72            website_url: website_url.into(),
73            metadata,
74            proxy,
75            user_agent: None,
76        }
77    }
78
79    pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
80        self.user_agent = Some(value.into());
81        self
82    }
83}
84
85impl CaptchaRequest for DataDomeCustomTaskRequest {
86    const TASK_TYPE: &'static str = "CustomTask";
87    const CLASS: Option<&'static str> = Some("DataDome");
88
89    fn timeouts(&self) -> GetResultTimeouts {
90        GetResultTimeouts::secs(1.0, 0.0, 3.0, 180.0)
91    }
92
93    fn validate(&self) -> Result<()> {
94        require_non_empty("websiteURL", &self.website_url)?;
95        require_non_empty("metadata.captchaUrl", &self.metadata.captcha_url)?;
96        require_non_empty("metadata.datadomeCookie", &self.metadata.datadome_cookie)?;
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::proxy::ProxyType;
105    use serde_json::json;
106
107    fn proxy() -> ProxyInfo {
108        ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080).with_auth("u", "p")
109    }
110
111    #[test]
112    fn minimal_task() {
113        let task = DataDomeCustomTaskRequest::new(
114            "https://example.com",
115            DataDomeMetadata::new(
116                "https://geo.captcha-delivery.com/captcha/?t=fe",
117                "datadome=abc",
118            ),
119            proxy(),
120        )
121        .to_task()
122        .unwrap();
123        assert_eq!(
124            task,
125            json!({
126                "type": "CustomTask", "class": "DataDome", "websiteURL": "https://example.com",
127                "metadata": {"captchaUrl": "https://geo.captcha-delivery.com/captcha/?t=fe", "datadomeCookie": "datadome=abc"},
128                "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 8080,
129                "proxyLogin": "u", "proxyPassword": "p"
130            })
131        );
132    }
133
134    #[test]
135    fn full_task() {
136        let task = DataDomeCustomTaskRequest::new(
137            "https://example.com",
138            DataDomeMetadata::new("U", "C").with_datadome_version(DataDomeVersion::New),
139            proxy(),
140        )
141        .with_user_agent("UA")
142        .to_task()
143        .unwrap();
144        assert_eq!(
145            task,
146            json!({
147                "type": "CustomTask", "class": "DataDome", "websiteURL": "https://example.com",
148                "metadata": {"captchaUrl": "U", "datadomeCookie": "C", "datadomeVersion": "new"},
149                "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 8080,
150                "proxyLogin": "u", "proxyPassword": "p", "userAgent": "UA"
151            })
152        );
153    }
154
155    #[test]
156    fn empty_required_strings_are_rejected() {
157        let err = DataDomeCustomTaskRequest::new(
158            "",
159            DataDomeMetadata::new("U", "C"),
160            ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080),
161        )
162        .to_task()
163        .unwrap_err();
164        assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
165        let err = DataDomeCustomTaskRequest::new(
166            "https://example.com",
167            DataDomeMetadata::new("", "C"),
168            ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080),
169        )
170        .to_task()
171        .unwrap_err();
172        assert!(
173            matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.captchaUrl"))
174        );
175        let err = DataDomeCustomTaskRequest::new(
176            "https://example.com",
177            DataDomeMetadata::new("U", ""),
178            ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080),
179        )
180        .to_task()
181        .unwrap_err();
182        assert!(
183            matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.datadomeCookie"))
184        );
185    }
186}