captchaforge 0.2.31

Automatic CAPTCHA detection and multi-strategy solving for chromiumoxide-driven headless browsers (Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha, image grids, audio, sliders).
Documentation
//! Cloudflare Turnstile detector.
//!
//! Two probes: the `.cf-turnstile` container with `data-sitekey`, and a
//! script tag pointing at `challenges.cloudflare.com` plus a generic
//! `[data-sitekey]` element that no other provider has claimed.
use anyhow::Result;
use async_trait::async_trait;
use chromiumoxide::Page;

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

/// Detector for Cloudflare Turnstile widgets.
pub struct TurnstileDetector;

impl TurnstileDetector {
    /// JS payload evaluated against the live page.
    pub const JS: &'static str = r#"(function() {
    const turnstile = document.querySelector('.cf-turnstile, [data-turnstile-sitekey]');
    if (turnstile) {
        return {
            kind: 'turnstile',
            site_key: turnstile.getAttribute('data-sitekey') ||
                      turnstile.getAttribute('data-turnstile-sitekey'),
            container: '.cf-turnstile'
        };
    }
    const turnstileScript = document.querySelector('script[src*="challenges.cloudflare.com"]');
    if (turnstileScript) {
        const cfEl = document.querySelector('[data-sitekey]');
        if (cfEl) {
            return {
                kind: 'turnstile',
                site_key: cfEl.getAttribute('data-sitekey'),
                container: '[data-sitekey]'
            };
        }
    }
    return { kind: 'none', site_key: null, container: null };
})()"#;
}

#[async_trait]
impl Detector for TurnstileDetector {
    fn name(&self) -> &'static str {
        "turnstile"
    }
    fn priority(&self) -> i32 {
        10
    }
    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 turnstile_detector_js_has_selectors() {
        assert!(TurnstileDetector::JS.contains(".cf-turnstile"));
        assert!(TurnstileDetector::JS.contains("[data-turnstile-sitekey]"));
        assert!(TurnstileDetector::JS.contains("challenges.cloudflare.com"));
    }
}