Skip to main content

chaser_gt/
models.rs

1//! Data models for Geetest v4 captcha.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Supported captcha risk types.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RiskType {
9    /// Slide puzzle captcha
10    Slide,
11    /// Gobang (Five-in-a-row) puzzle
12    Gobang,
13    /// Icon selection captcha
14    Icon,
15    /// AI/Invisible captcha
16    Ai,
17    /// SVG animated icon captcha
18    Svg,
19}
20
21impl RiskType {
22    /// Returns the string representation for API calls.
23    pub fn as_str(&self) -> &'static str {
24        match self {
25            RiskType::Slide => "slide",
26            RiskType::Gobang => "gobang",
27            RiskType::Icon => "icon",
28            RiskType::Ai => "ai",
29            RiskType::Svg => "svg",
30        }
31    }
32}
33
34impl std::fmt::Display for RiskType {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.as_str())
37    }
38}
39
40/// Response from successful captcha solve.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SecCode {
43    pub captcha_id: String,
44    pub lot_number: String,
45    pub pass_token: String,
46    pub gen_time: String,
47    pub captcha_output: String,
48}
49
50/// Raw response wrapper from Geetest API (JSONP format).
51#[derive(Debug, Deserialize)]
52pub struct GeetestResponse<T> {
53    pub status: String,
54    /// Data field - only present on success
55    pub data: Option<T>,
56    /// Error code (present when status != "success")
57    #[serde(default)]
58    pub code: Option<String>,
59    /// Error message (present when status != "success")  
60    #[serde(default)]
61    pub msg: Option<String>,
62}
63
64/// Response from /load endpoint.
65#[derive(Debug, Clone, Deserialize)]
66pub struct LoadResponse {
67    pub lot_number: String,
68    pub payload: String,
69    pub process_token: String,
70    pub pt: String,
71    pub pow_detail: PowDetail,
72    // Slide-specific
73    #[serde(default)]
74    pub slice: Option<String>,
75    #[serde(default)]
76    pub bg: Option<String>,
77    // Gobang-specific
78    #[serde(default)]
79    pub ques: Option<serde_json::Value>,
80    // Icon-specific
81    #[serde(default)]
82    pub imgs: Option<String>,
83    // SVG-specific
84    #[serde(default)]
85    pub svg: Option<String>,
86    #[serde(default)]
87    pub prompt: Option<String>,
88}
89
90/// Proof of Work details from server.
91#[derive(Debug, Clone, Deserialize)]
92pub struct PowDetail {
93    pub hashfunc: String,
94    pub version: String,
95    pub bits: u32,
96    pub datetime: String,
97}
98
99/// Proof of Work result.
100#[derive(Debug, Clone, Serialize)]
101pub struct PowResult {
102    pub pow_msg: String,
103    pub pow_sign: String,
104}
105
106/// Response from /verify endpoint.
107#[derive(Debug, Deserialize)]
108pub struct VerifyResponse {
109    pub seccode: Option<SecCode>,
110    #[serde(default)]
111    pub result: Option<String>,
112    /// Score from verification (can be string or integer)
113    #[serde(default, deserialize_with = "deserialize_optional_string_or_int")]
114    pub score: Option<String>,
115    /// Updated payload for continue responses
116    #[serde(default)]
117    pub payload: Option<String>,
118    /// Updated process_token for continue responses
119    #[serde(default)]
120    pub process_token: Option<String>,
121    /// Updated payload_protocol for continue responses (can be string or integer)
122    #[serde(default, deserialize_with = "deserialize_optional_string_or_int")]
123    pub payload_protocol: Option<String>,
124    /// Updated lot_number for continue responses
125    #[serde(default)]
126    pub lot_number: Option<String>,
127}
128
129/// Helper to deserialize fields that can be either string or integer
130fn deserialize_optional_string_or_int<'de, D>(
131    deserializer: D,
132) -> std::result::Result<Option<String>, D::Error>
133where
134    D: serde::Deserializer<'de>,
135{
136    use serde::de::{self, Visitor};
137
138    struct StringOrIntVisitor;
139
140    impl<'de> Visitor<'de> for StringOrIntVisitor {
141        type Value = Option<String>;
142
143        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
144            formatter.write_str("a string, integer, or null")
145        }
146
147        fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
148        where
149            E: de::Error,
150        {
151            Ok(None)
152        }
153
154        fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
155        where
156            E: de::Error,
157        {
158            Ok(None)
159        }
160
161        fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
162        where
163            E: de::Error,
164        {
165            Ok(Some(v.to_string()))
166        }
167
168        fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
169        where
170            E: de::Error,
171        {
172            Ok(Some(v))
173        }
174
175        fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
176        where
177            E: de::Error,
178        {
179            Ok(Some(v.to_string()))
180        }
181
182        fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
183        where
184            E: de::Error,
185        {
186            Ok(Some(v.to_string()))
187        }
188    }
189
190    deserializer.deserialize_any(StringOrIntVisitor)
191}
192
193/// Cached constants from deobfuscation.
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct CachedConstants {
196    /// Geetest script version (e.g., "v1.9.3-26b399")
197    pub version: String,
198    /// When the constants were fetched
199    pub fetched_at: chrono::DateTime<chrono::Utc>,
200    /// LotParser mapping pattern
201    pub mapping: String,
202    /// Additional constants (abo)
203    pub abo: HashMap<String, String>,
204    /// Device ID (usually empty)
205    pub device_id: String,
206}
207
208/// Runtime constants used for signing.
209#[derive(Debug, Clone)]
210pub struct Constants {
211    pub mapping: String,
212    pub abo: HashMap<String, String>,
213    pub device_id: String,
214}
215
216impl From<CachedConstants> for Constants {
217    fn from(cached: CachedConstants) -> Self {
218        Self {
219            mapping: cached.mapping,
220            abo: cached.abo,
221            device_id: cached.device_id,
222        }
223    }
224}