1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! captchaforge — automatic CAPTCHA detection and solving for
//! `chromiumoxide`-driven headless browsers.
//!
//! Extracted from [golemn-browser](https://github.com/santhsecurity/golemn)
//! (originally GPL-3.0) and re-licensed MIT OR Apache-2.0 by the
//! original author so it can stand alone and be embedded by other
//! Santh-ecosystem tools (wafrift, gossan, sear, …).
//!
//! Two modules:
//! - [`detect`] — heuristic + DOM-based CAPTCHA identification
//! - [`solver`] — multi-strategy solving chain
//! - **Behavioural** — realistic mouse + timing for Turnstile /
//! reCAPTCHA v3
//! - **Vision-LLM** — screenshot → multimodal model → click /
//! type the answer
//! - **Audio bypass** — accessibility audio challenge → STT →
//! transcribed answer
//! - **Crowd-sourced** — per-domain pattern memory of which
//! method has succeeded before
//! - **Behavioural / human fallback** — return unsolved for
//! human-in-the-loop
//!
//! The crate exposes a `CaptchaSolver` trait so consumers can plug
//! in their own solver strategies without forking.
//!
//! # Example — detect and solve
//!
//! ```rust,no_run
//! use captchaforge::{detect, solver::CaptchaSolverChain};
//! # async fn run(page: &chromiumoxide::Page) -> anyhow::Result<()> {
//! let info = detect::detect(page).await?;
//! if detect::is_captcha(&info) {
//! let chain = CaptchaSolverChain::default_chain();
//! // chain.solve(page, &info).await?;
//! }
//! # Ok(()) }
//! ```
//!
//! # Example — pattern store
//!
//! ```rust
//! use captchaforge::solver::{CaptchaType, PatternStore, SolveMethod};
//!
//! let store = PatternStore::default();
//! store.record("example.com", &CaptchaType::CloudflareTurnstile, true, 1200, SolveMethod::BehavioralBypass);
//! assert_eq!(store.best_method("example.com", &CaptchaType::CloudflareTurnstile), Some(SolveMethod::BehavioralBypass));
//! ```
//!
//! # Example — frame evaluation
//!
//! ```rust,no_run
//! use captchaforge::frame::evaluate_in_all_frames;
//! # async fn run(page: &chromiumoxide::Page) -> anyhow::Result<()> {
//! let titles: Vec<String> = evaluate_in_all_frames(page, "document.title").await?;
//! # Ok(()) }
//! ```
//!
//! # Example — behavior simulation
//!
//! ```rust,no_run
//! use captchaforge::behavior::{mouse_move_bezier, click_realistic};
//! # async fn run(page: &chromiumoxide::Page) -> anyhow::Result<()> {
//! mouse_move_bezier(page, 0.0, 0.0, 400.0, 300.0).await?;
//! click_realistic(page, 400.0, 300.0).await?;
//! # Ok(()) }
//! ```
pub use Config;
pub use CapturedCookie;
// Back-compat re-export so code that used `golemn_browser::captcha_detect`
// can `use captchaforge::captcha_detect` instead.
pub use detect as captcha_detect;
pub use ;
/// One-call convenience: detect + solve in a single API.
///
/// Equivalent to:
///
/// ```ignore
/// let info = captchaforge::detect::detect(page).await?;
/// if !captchaforge::detect::is_captcha(&info) {
/// return Ok(None);
/// }
/// let chain = captchaforge::solver::CaptchaSolverChain::default_chain();
/// Ok(Some(chain.solve(page, &info).await))
/// ```
///
/// Returns:
/// * `Ok(None)` — no captcha was detected on the page.
/// * `Ok(Some(result))` — captcha detected; `result.success` indicates
/// whether the chain solved it. Failed solves still return Some
/// so the caller can inspect the screenshot for human fallback.
/// * `Err(_)` — the detection step itself failed (page eval error,
/// CDP transport error). Solver errors are swallowed into a
/// no-op `CaptchaSolveResult` and don't surface here.
///
/// Power users that need a custom chain (telemetry, token cache,
/// pattern store, custom solvers) construct their own
/// [`solver::CaptchaSolverChain`] and call `chain.solve()` directly.
pub async