capmonstercloud_client/requests/
alibaba.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)]
11pub struct AlibabaMetadata {
12 #[serde(rename = "sceneId")]
14 pub scene_id: String,
15 pub prefix: String,
18 #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
20 pub user_id: Option<String>,
21 #[serde(rename = "userUserId", skip_serializing_if = "Option::is_none")]
23 pub user_user_id: Option<String>,
24 #[serde(rename = "verifyType", skip_serializing_if = "Option::is_none")]
26 pub verify_type: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub region: Option<String>,
30 #[serde(rename = "UserCertifyId", skip_serializing_if = "Option::is_none")]
32 pub user_certify_id: Option<String>,
33 #[serde(rename = "apiGetLib", skip_serializing_if = "Option::is_none")]
35 pub api_get_lib: Option<String>,
36 #[serde(rename = "punishUrl", skip_serializing_if = "Option::is_none")]
39 pub punish_url: Option<String>,
40 #[serde(rename = "cookieRequired", skip_serializing_if = "Option::is_none")]
42 pub cookie_required: Option<bool>,
43}
44
45impl AlibabaMetadata {
46 pub fn new(scene_id: impl Into<String>, prefix: impl Into<String>) -> Self {
47 Self {
48 scene_id: scene_id.into(),
49 prefix: prefix.into(),
50 user_id: None,
51 user_user_id: None,
52 verify_type: None,
53 region: None,
54 user_certify_id: None,
55 api_get_lib: None,
56 punish_url: None,
57 cookie_required: None,
58 }
59 }
60
61 pub fn with_user_id(mut self, value: impl Into<String>) -> Self {
62 self.user_id = Some(value.into());
63 self
64 }
65
66 pub fn with_user_user_id(mut self, value: impl Into<String>) -> Self {
67 self.user_user_id = Some(value.into());
68 self
69 }
70
71 pub fn with_verify_type(mut self, value: impl Into<String>) -> Self {
72 self.verify_type = Some(value.into());
73 self
74 }
75
76 pub fn with_region(mut self, value: impl Into<String>) -> Self {
77 self.region = Some(value.into());
78 self
79 }
80
81 pub fn with_user_certify_id(mut self, value: impl Into<String>) -> Self {
82 self.user_certify_id = Some(value.into());
83 self
84 }
85
86 pub fn with_api_get_lib(mut self, value: impl Into<String>) -> Self {
87 self.api_get_lib = Some(value.into());
88 self
89 }
90
91 pub fn with_punish_url(mut self, value: impl Into<String>) -> Self {
92 self.punish_url = Some(value.into());
93 self
94 }
95
96 pub fn with_cookie_required(mut self, value: bool) -> Self {
97 self.cookie_required = Some(value);
98 self
99 }
100}
101
102#[derive(Debug, Clone, Serialize)]
104pub struct AlibabaCustomTaskRequest {
105 #[serde(rename = "websiteURL")]
107 pub website_url: String,
108 pub metadata: AlibabaMetadata,
109 #[serde(flatten, skip_serializing_if = "Option::is_none")]
110 pub proxy: Option<ProxyInfo>,
111 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
113 pub user_agent: Option<String>,
114}
115
116impl AlibabaCustomTaskRequest {
117 pub fn new(website_url: impl Into<String>, metadata: AlibabaMetadata) -> Self {
118 Self {
119 website_url: website_url.into(),
120 metadata,
121 proxy: None,
122 user_agent: None,
123 }
124 }
125
126 pub fn with_proxy(mut self, proxy: ProxyInfo) -> Self {
127 self.proxy = Some(proxy);
128 self
129 }
130
131 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
132 self.user_agent = Some(value.into());
133 self
134 }
135}
136
137impl CaptchaRequest for AlibabaCustomTaskRequest {
138 const TASK_TYPE: &'static str = "CustomTask";
139 const CLASS: Option<&'static str> = Some("alibaba");
140
141 fn timeouts(&self) -> GetResultTimeouts {
142 GetResultTimeouts::secs(1.0, 0.0, 3.0, 120.0)
143 }
144
145 fn validate(&self) -> Result<()> {
146 require_non_empty("websiteURL", &self.website_url)?;
147 require_non_empty("metadata.sceneId", &self.metadata.scene_id)?;
148 require_non_empty("metadata.prefix", &self.metadata.prefix)?;
149 Ok(())
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::proxy::ProxyType;
157 use serde_json::json;
158
159 #[test]
160 fn minimal_task() {
161 let task = AlibabaCustomTaskRequest::new(
162 "https://example.com",
163 AlibabaMetadata::new("1ww7426c", "dlw3kug"),
164 )
165 .to_task()
166 .unwrap();
167 assert_eq!(
168 task,
169 json!({
170 "type": "CustomTask", "class": "alibaba", "websiteURL": "https://example.com",
171 "metadata": {"sceneId": "1ww7426c", "prefix": "dlw3kug"}
172 })
173 );
174 }
175
176 #[test]
177 fn full_task() {
178 let meta = AlibabaMetadata::new("S", "P")
179 .with_user_id("U")
180 .with_user_user_id("UU")
181 .with_verify_type("1.0")
182 .with_region("sgp")
183 .with_user_certify_id("CID")
184 .with_api_get_lib("https://x/AliyunCaptcha.js")
185 .with_punish_url("https://x/punish?a=b")
186 .with_cookie_required(true);
187 let task = AlibabaCustomTaskRequest::new("https://example.com", meta)
188 .with_proxy(ProxyInfo::new(ProxyType::Http, "1.2.3.4", 80))
189 .with_user_agent("UA")
190 .to_task()
191 .unwrap();
192 assert_eq!(
193 task,
194 json!({
195 "type": "CustomTask", "class": "alibaba", "websiteURL": "https://example.com",
196 "metadata": {
197 "sceneId": "S", "prefix": "P", "userId": "U", "userUserId": "UU",
198 "verifyType": "1.0", "region": "sgp", "UserCertifyId": "CID",
199 "apiGetLib": "https://x/AliyunCaptcha.js", "punishUrl": "https://x/punish?a=b",
200 "cookieRequired": true
201 },
202 "proxyType": "http", "proxyAddress": "1.2.3.4", "proxyPort": 80, "userAgent": "UA"
203 })
204 );
205 }
206
207 #[test]
208 fn empty_required_strings_are_rejected() {
209 let err = AlibabaCustomTaskRequest::new("", AlibabaMetadata::new("S", "P"))
210 .to_task()
211 .unwrap_err();
212 assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
213 let err =
214 AlibabaCustomTaskRequest::new("https://example.com", AlibabaMetadata::new("", "P"))
215 .to_task()
216 .unwrap_err();
217 assert!(
218 matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.sceneId"))
219 );
220 let err =
221 AlibabaCustomTaskRequest::new("https://example.com", AlibabaMetadata::new("S", ""))
222 .to_task()
223 .unwrap_err();
224 assert!(
225 matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.prefix"))
226 );
227 }
228}