captchaforge 0.2.39

Captcha detection and solving for Firefox and BiDi-driven browsers. Detection, vendor solver scaffolding, trusted cross-origin click delivery into nested OOPIFs, and stealth personas are implemented and tested; broad live-vendor solve rates are not yet benchmarked.
Documentation
//! Proof-of-Work CAPTCHA detector, covers ALTCHA, mCaptcha,
//! Friendly Captcha, and Cap.dev.
//!
//! These share a model: the browser computes a hash chain instead of
//! presenting a visual challenge. They are detected by container
//! class/data attribute or by the presence of their loader script.
use crate::Page;
use anyhow::Result;
use async_trait::async_trait;

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

/// Detector for PoW-based CAPTCHAs (ALTCHA, mCaptcha, Friendly Captcha,
/// Cap.dev).
pub struct PowCaptchaDetector;

impl PowCaptchaDetector {
    /// JS payload evaluated against the live page.
    pub const JS: &'static str = r#"(function() {
    const selectors = [
        '.altcha', '[data-altcha]',
        '.mcaptcha', '[data-mcaptcha]',
        '.friendly-captcha', '[data-friendly-captcha]',
        '.cap-dev', '[data-cap-dev]'
    ];
    for (const sel of selectors) {
        if (document.querySelector(sel)) {
            return { kind: 'pow', site_key: null, container: sel };
        }
    }
    const scripts = document.querySelectorAll('script[src]');
    for (const s of scripts) {
        const src = s.getAttribute('src') || '';
        if (src.includes('altcha') || src.includes('mcaptcha') || src.includes('friendlycaptcha') || src.includes('cap.dev')) {
            const match = src.includes('altcha') ? 'altcha'
                : src.includes('mcaptcha') ? 'mcaptcha'
                : src.includes('friendlycaptcha') ? 'friendlycaptcha'
                : 'cap.dev';
            return { kind: 'pow', site_key: null, container: 'script[src*="' + match + '"]' };
        }
    }
    return { kind: 'none', site_key: null, container: null };
})()"#;
}

#[async_trait]
impl Detector for PowCaptchaDetector {
    fn name(&self) -> &'static str {
        "pow_captcha"
    }
    fn priority(&self) -> i32 {
        60
    }
    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 pow_captcha_detector_js_has_selectors() {
        assert!(PowCaptchaDetector::JS.contains(".altcha"));
        assert!(PowCaptchaDetector::JS.contains("[data-altcha]"));
        assert!(PowCaptchaDetector::JS.contains(".mcaptcha"));
        assert!(PowCaptchaDetector::JS.contains("[data-mcaptcha]"));
        assert!(PowCaptchaDetector::JS.contains(".friendly-captcha"));
        assert!(PowCaptchaDetector::JS.contains("[data-friendly-captcha]"));
        assert!(PowCaptchaDetector::JS.contains(".cap-dev"));
        assert!(PowCaptchaDetector::JS.contains("[data-cap-dev]"));
    }
}