capmonstercloud_client/requests/
funcaptcha.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)]
13pub struct FuncaptchaRequest {
14 #[serde(rename = "websiteURL")]
16 pub website_url: String,
17 #[serde(rename = "websitePublicKey")]
19 pub website_public_key: String,
20 #[serde(flatten)]
22 pub proxy: ProxyInfo,
23 #[serde(
26 rename = "funcaptchaApiJSSubdomain",
27 skip_serializing_if = "Option::is_none"
28 )]
29 pub funcaptcha_api_js_subdomain: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub data: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub cookies: Option<String>,
36 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
38 pub user_agent: Option<String>,
39}
40
41impl FuncaptchaRequest {
42 pub fn new(
43 website_url: impl Into<String>,
44 website_public_key: impl Into<String>,
45 proxy: ProxyInfo,
46 ) -> Self {
47 Self {
48 website_url: website_url.into(),
49 website_public_key: website_public_key.into(),
50 proxy,
51 funcaptcha_api_js_subdomain: None,
52 data: None,
53 cookies: None,
54 user_agent: None,
55 }
56 }
57
58 pub fn with_funcaptcha_api_js_subdomain(mut self, value: impl Into<String>) -> Self {
59 self.funcaptcha_api_js_subdomain = Some(value.into());
60 self
61 }
62
63 pub fn with_data(mut self, value: impl Into<String>) -> Self {
64 self.data = Some(value.into());
65 self
66 }
67
68 pub fn with_cookies(mut self, value: impl Into<String>) -> Self {
69 self.cookies = Some(value.into());
70 self
71 }
72
73 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
74 self.user_agent = Some(value.into());
75 self
76 }
77}
78
79impl CaptchaRequest for FuncaptchaRequest {
80 const TASK_TYPE: &'static str = "FunCaptchaTask";
81
82 fn timeouts(&self) -> GetResultTimeouts {
83 GetResultTimeouts::secs(1.0, 10.0, 1.0, 80.0)
84 }
85
86 fn validate(&self) -> Result<()> {
87 require_non_empty("websiteURL", &self.website_url)?;
88 require_non_empty("websitePublicKey", &self.website_public_key)?;
89 Ok(())
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::proxy::ProxyType;
97 use serde_json::json;
98
99 fn proxy() -> ProxyInfo {
100 ProxyInfo::new(ProxyType::Http, "1.2.3.4", 8080)
101 }
102
103 #[test]
104 fn minimal_task() {
105 let task = FuncaptchaRequest::new("https://example.com", "PK", proxy())
106 .to_task()
107 .unwrap();
108 assert_eq!(
109 task,
110 json!({"type": "FunCaptchaTask", "websiteURL": "https://example.com",
111 "websitePublicKey": "PK",
112 "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 8080})
113 );
114 }
115
116 #[test]
117 fn full_task() {
118 let task = FuncaptchaRequest::new("https://example.com", "PK", proxy().with_auth("u", "p"))
119 .with_funcaptcha_api_js_subdomain("x-api.arkoselabs.com")
120 .with_data("{\"blob\":\"B\"}")
121 .with_cookies("a=b")
122 .with_user_agent("UA")
123 .to_task()
124 .unwrap();
125 assert_eq!(
126 task,
127 json!({"type": "FunCaptchaTask", "websiteURL": "https://example.com",
128 "websitePublicKey": "PK",
129 "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 8080,
130 "proxyLogin": "u", "proxyPassword": "p",
131 "funcaptchaApiJSSubdomain": "x-api.arkoselabs.com",
132 "data": "{\"blob\":\"B\"}", "cookies": "a=b", "userAgent": "UA"})
133 );
134 }
135
136 #[test]
137 fn empty_required_strings_are_rejected() {
138 let err = FuncaptchaRequest::new("", "PK", proxy())
139 .to_task()
140 .unwrap_err();
141 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
142 let err = FuncaptchaRequest::new("https://example.com", "", proxy())
143 .to_task()
144 .unwrap_err();
145 assert!(
146 matches!(err, crate::error::Error::Validation(ref m) if m.contains("websitePublicKey"))
147 );
148 }
149}