Skip to main content

capmonstercloud_client/requests/
mod.rs

1//! One struct per captcha type. Every struct implements [`CaptchaRequest`].
2//!
3//! Conventions used by all request types (keep them when adding a new one):
4//! * `Type::new(<required fields>)` builds a request; optional fields are `Option<_>` public
5//!   fields with chainable `with_<field>(value)` setters.
6//! * Field names are `snake_case`; `#[serde(rename = "...")]` maps them to the exact API names
7//!   (note `websiteURL`, not `websiteUrl`). `None` fields are not sent.
8//! * `"type"` (and `"class"` for CustomTask) come from `TASK_TYPE` / `CLASS`, never from a field.
9//! * Local checks that the API would reject anyway live in `validate()`.
10
11use serde::Serialize;
12use serde_json::Value;
13
14use crate::error::{Error, Result};
15use crate::timeouts::GetResultTimeouts;
16
17mod alibaba;
18mod altcha;
19mod amazon_waf;
20mod basilisk;
21mod binance;
22mod complex_image;
23mod datadome;
24mod friendly;
25mod funcaptcha;
26mod geetest;
27mod hunt;
28mod image_to_text;
29mod imperva;
30mod mt_captcha;
31mod prosopo;
32mod recaptcha_complex_image;
33mod recaptcha_v2;
34mod recaptcha_v2_enterprise;
35mod recaptcha_v3;
36mod recaptcha_v3_enterprise;
37mod recognition_complex_image;
38mod ten_di;
39mod tspd;
40mod turnstile;
41mod yidun;
42
43pub use alibaba::{AlibabaCustomTaskRequest, AlibabaMetadata};
44pub use altcha::{AltchaCustomTaskRequest, AltchaMetadata};
45pub use amazon_waf::{
46    AmazonChallengeAndCaptcha, AmazonChallengeOnly, AmazonVisibleCaptcha, AmazonWafRequest,
47    AmazonWafVariant,
48};
49pub use basilisk::BasiliskCustomTaskRequest;
50pub use binance::BinanceTaskRequest;
51pub use datadome::{DataDomeCustomTaskRequest, DataDomeMetadata, DataDomeVersion};
52pub use friendly::{FriendlyCustomTaskRequest, FriendlyMetadata};
53pub use funcaptcha::FuncaptchaRequest;
54pub use geetest::{GeetestRequest, GeetestVersion};
55pub use hunt::{HuntCustomTaskRequest, HuntMetadata, HuntMode};
56pub use image_to_text::{ImageToTextRequest, TextModule};
57pub use imperva::{ImpervaCustomTaskRequest, ImpervaMetadata};
58pub use mt_captcha::MtCaptchaRequest;
59pub use prosopo::ProsopoTaskRequest;
60pub use recaptcha_complex_image::{
61    RecaptchaComplexImageTaskRequest, RecaptchaGrid, RecaptchaMetadata,
62};
63pub use recaptcha_v2::RecaptchaV2Request;
64pub use recaptcha_v2_enterprise::{EnterprisePayload, RecaptchaV2EnterpriseRequest};
65pub use recaptcha_v3::RecaptchaV3ProxylessRequest;
66pub use recaptcha_v3_enterprise::RecaptchaV3EnterpriseRequest;
67pub use recognition_complex_image::{
68    PayloadType, RecognitionComplexImageTaskRequest, RecognitionMetadata,
69};
70pub use ten_di::{TenDiCustomTaskRequest, TenDiMetadata};
71pub use tspd::{TspdCustomTaskRequest, TspdMetadata};
72pub use turnstile::{
73    TurnstileChallengeCookie, TurnstileChallengeToken, TurnstileMode, TurnstileRequest,
74    TurnstileStandard, TurnstileWaitRoom,
75};
76pub use yidun::YidunRequest;
77
78/// Fails with [`Error::Validation`] when a required string field is empty.
79pub(crate) fn require_non_empty(field: &str, value: &str) -> Result<()> {
80    if value.is_empty() {
81        return Err(Error::Validation(format!("{field} must not be empty")));
82    }
83    Ok(())
84}
85
86/// A task that can be sent to CapMonster Cloud.
87pub trait CaptchaRequest: Serialize {
88    /// Value of the `"type"` field of the task.
89    const TASK_TYPE: &'static str;
90    /// Value of the `"class"` field (only for `CustomTask`).
91    const CLASS: Option<&'static str> = None;
92
93    /// Polling schedule recommended for this captcha type.
94    fn timeouts(&self) -> GetResultTimeouts;
95
96    /// Extra consistency checks, run before the request is sent.
97    fn validate(&self) -> Result<()> {
98        Ok(())
99    }
100
101    /// The `task` JSON object for `createTask`.
102    fn to_task(&self) -> Result<Value> {
103        self.validate()?;
104        let mut task = serde_json::to_value(self)?;
105        let obj = task
106            .as_object_mut()
107            .ok_or_else(|| Error::Validation("task must serialize to a JSON object".into()))?;
108        obj.insert("type".into(), Self::TASK_TYPE.into());
109        if let Some(class) = Self::CLASS {
110            obj.insert("class".into(), class.into());
111        }
112        Ok(task)
113    }
114}