capmonstercloud_client/requests/
recaptcha_v2.rs1use serde::Serialize;
2
3use super::{require_non_empty, CaptchaRequest};
4use crate::error::Result;
5use crate::proxy::ProxyInfo;
6use crate::timeouts::GetResultTimeouts;
7
8#[derive(Debug, Clone, Serialize)]
10pub struct RecaptchaV2Request {
11 #[serde(rename = "websiteURL")]
13 pub website_url: String,
14 #[serde(rename = "websiteKey")]
16 pub website_key: String,
17 #[serde(flatten, skip_serializing_if = "Option::is_none")]
18 pub proxy: Option<ProxyInfo>,
19 #[serde(
21 rename = "recaptchaDataSValue",
22 skip_serializing_if = "Option::is_none"
23 )]
24 pub data_s_value: Option<String>,
25 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
27 pub user_agent: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub cookies: Option<String>,
31 #[serde(rename = "isInvisible", skip_serializing_if = "Option::is_none")]
32 pub is_invisible: Option<bool>,
33}
34
35impl RecaptchaV2Request {
36 pub fn new(website_url: impl Into<String>, website_key: impl Into<String>) -> Self {
37 Self {
38 website_url: website_url.into(),
39 website_key: website_key.into(),
40 proxy: None,
41 data_s_value: None,
42 user_agent: None,
43 cookies: None,
44 is_invisible: None,
45 }
46 }
47
48 pub fn with_proxy(mut self, proxy: ProxyInfo) -> Self {
49 self.proxy = Some(proxy);
50 self
51 }
52
53 pub fn with_data_s_value(mut self, value: impl Into<String>) -> Self {
54 self.data_s_value = Some(value.into());
55 self
56 }
57
58 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
59 self.user_agent = Some(value.into());
60 self
61 }
62
63 pub fn with_cookies(mut self, value: impl Into<String>) -> Self {
64 self.cookies = Some(value.into());
65 self
66 }
67
68 pub fn with_is_invisible(mut self, value: bool) -> Self {
69 self.is_invisible = Some(value);
70 self
71 }
72}
73
74impl CaptchaRequest for RecaptchaV2Request {
75 const TASK_TYPE: &'static str = "RecaptchaV2Task";
76
77 fn timeouts(&self) -> GetResultTimeouts {
78 GetResultTimeouts::secs(1.0, 10.0, 3.0, 180.0)
79 }
80
81 fn validate(&self) -> Result<()> {
82 require_non_empty("websiteURL", &self.website_url)?;
83 require_non_empty("websiteKey", &self.website_key)?;
84 Ok(())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::proxy::{ProxyInfo, ProxyType};
92 use serde_json::json;
93
94 #[test]
95 fn minimal_task() {
96 let task = RecaptchaV2Request::new("https://example.com", "KEY")
97 .to_task()
98 .unwrap();
99 assert_eq!(
100 task,
101 json!({"type": "RecaptchaV2Task", "websiteURL": "https://example.com", "websiteKey": "KEY"})
102 );
103 }
104
105 #[test]
106 fn full_task_flattens_proxy_and_renames_fields() {
107 let task = RecaptchaV2Request::new("https://example.com", "KEY")
108 .with_proxy(ProxyInfo::new(ProxyType::Socks5, "1.2.3.4", 1080).with_auth("u", "p"))
109 .with_data_s_value("S")
110 .with_user_agent("UA")
111 .with_cookies("a=b")
112 .with_is_invisible(true)
113 .to_task()
114 .unwrap();
115 assert_eq!(
116 task,
117 json!({
118 "type": "RecaptchaV2Task", "websiteURL": "https://example.com", "websiteKey": "KEY",
119 "proxyType": "socks5", "proxyAddress": "1.2.3.4", "proxyPort": 1080,
120 "proxyLogin": "u", "proxyPassword": "p",
121 "recaptchaDataSValue": "S", "userAgent": "UA", "cookies": "a=b", "isInvisible": true
122 })
123 );
124 }
125
126 #[test]
127 fn empty_required_strings_are_rejected() {
128 let err = RecaptchaV2Request::new("", "KEY").to_task().unwrap_err();
129 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
130 let err = RecaptchaV2Request::new("https://example.com", "")
131 .to_task()
132 .unwrap_err();
133 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteKey")));
134 }
135}