captchaforge 0.2.36

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for chromiumoxide-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / CDP fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! Cloudflare interstitial challenge page detector.
//!
//! Fires before any other detector (lowest priority value) so a CF
//! "Just a moment..." page is recognised even when the embedded
//! Turnstile widget is not yet rendered. Maps the kind to
//! `DetectedCaptcha::Turnstile` because that is the captcha the
//! interstitial leads to.
use anyhow::Result;
use async_trait::async_trait;
use chromiumoxide::Page;

use super::{parse_detection_result, CaptchaInfo, Detector};

/// Detector for the Cloudflare interstitial challenge page.
pub struct ChallengePageDetector;

impl ChallengePageDetector {
    /// JS payload evaluated against the live page to decide whether the
    /// document is a Cloudflare interstitial.
    pub const JS: &'static str = r#"(function() {
    if (document.title.includes('Just a moment') ||
        document.title.includes('Attention Required') ||
        document.querySelector('#challenge-running, #challenge-form, .cf-browser-verification')) {
        return { kind: 'turnstile', site_key: null, container: '#challenge-form' };
    }
    return { kind: 'none', site_key: null, container: null };
})()"#;
}

#[async_trait]
impl Detector for ChallengePageDetector {
    fn name(&self) -> &'static str {
        "challenge_page"
    }
    fn priority(&self) -> i32 {
        5
    }
    async fn detect(&self, page: &Page) -> Result<Option<CaptchaInfo>> {
        let raw = page.evaluate(Self::JS).await?;
        let val = raw.into_value::<serde_json::Value>()?;
        Ok(parse_detection_result(val))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn challenge_page_detector_js_has_selectors() {
        assert!(ChallengePageDetector::JS.contains("Just a moment"));
        assert!(ChallengePageDetector::JS.contains("Attention Required"));
        assert!(ChallengePageDetector::JS.contains("#challenge-running"));
        assert!(ChallengePageDetector::JS.contains("#challenge-form"));
    }
}