captchaforge 0.2.26

Automatic CAPTCHA detection and multi-strategy solving for chromiumoxide-driven headless browsers (Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha, image grids, audio, sliders).
Documentation
//! Shadow-DOM-hidden CAPTCHA detector.
//!
//! Walks every element with a `shadowRoot` and queries it for the
//! known captcha selectors from every other detector. Lets sites that
//! embed their captcha widget inside a shadow tree (web components,
//! design-system wrappers) still be detected, even though all other
//! detectors only query the light DOM.
use anyhow::Result;
use async_trait::async_trait;
use chromiumoxide::Page;

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

/// Detector for captchas hidden inside a shadow DOM.
pub struct ShadowDomDetector;

impl ShadowDomDetector {
    /// JS payload evaluated against the live page.
    pub const JS: &'static str = r#"(function() {
    function queryShadowDOM(selector) {
        const elems = document.querySelectorAll(selector);
        if (elems.length) return elems[0];
        const all = document.querySelectorAll('*');
        for (const el of all) {
            if (el.shadowRoot) {
                const found = el.shadowRoot.querySelector(selector);
                if (found) return found;
            }
        }
        return null;
    }
    const captchaSelectors = [
        '.cf-turnstile', '[data-turnstile-sitekey]',
        '.g-recaptcha', '[data-sitekey]',
        '.h-captcha', '[data-hcaptcha-sitekey]',
        '.altcha', '[data-altcha]',
        '.mcaptcha', '[data-mcaptcha]',
        '.friendly-captcha', '[data-friendly-captcha]',
        '.cap-dev', '[data-cap-dev]',
        '.slider-captcha', '#slider',
        'canvas.captcha', '#captcha canvas'
    ];
    for (const sel of captchaSelectors) {
        const found = queryShadowDOM(sel);
        if (found) {
            return { kind: 'shadow_dom', site_key: null, container: sel };
        }
    }
    return { kind: 'none', site_key: null, container: null };
})()"#;
}

#[async_trait]
impl Detector for ShadowDomDetector {
    fn name(&self) -> &'static str {
        "shadow_dom"
    }
    fn priority(&self) -> i32 {
        90
    }
    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 shadow_dom_detector_js_has_query_shadow_dom() {
        assert!(ShadowDomDetector::JS.contains("function queryShadowDOM(selector)"));
        assert!(ShadowDomDetector::JS.contains("el.shadowRoot"));
        assert!(ShadowDomDetector::JS.contains(".cf-turnstile"));
        assert!(ShadowDomDetector::JS.contains(".g-recaptcha"));
        assert!(ShadowDomDetector::JS.contains(".h-captcha"));
    }
}