captchaforge 0.2.4

Automatic CAPTCHA detection and multi-strategy solving for chromiumoxide-driven headless browsers (Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha, image grids, audio, sliders).
Documentation
//! Canvas / SVG / math / icon / color CAPTCHA detector.
//!
//! Targets in-house captcha widgets that render to a `<canvas>` or
//! `<svg>` element rather than using a SaaS provider.
use anyhow::Result;
use async_trait::async_trait;
use chromiumoxide::Page;

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

/// Detector for canvas/SVG/math/icon/color CAPTCHAs.
pub struct CanvasCaptchaDetector;

impl CanvasCaptchaDetector {
    /// JS payload evaluated against the live page.
    pub const JS: &'static str = r#"(function() {
    const canvas = document.querySelector('canvas.captcha, #captcha canvas, [class*="canvas-captcha"], [class*="svg-captcha"], [class*="math-captcha"], [class*="icon-captcha"], [class*="color-captcha"], [class*="rotated-text"]');
    if (canvas) {
        return { kind: 'canvas', site_key: null, container: 'canvas.captcha, #captcha canvas, [class*="canvas-captcha"], [class*="svg-captcha"], [class*="math-captcha"], [class*="icon-captcha"], [class*="color-captcha"], [class*="rotated-text"]' };
    }
    return { kind: 'none', site_key: null, container: null };
})()"#;
}

#[async_trait]
impl Detector for CanvasCaptchaDetector {
    fn name(&self) -> &'static str {
        "canvas_captcha"
    }
    fn priority(&self) -> i32 {
        65
    }
    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 canvas_captcha_detector_js_has_selectors() {
        assert!(CanvasCaptchaDetector::JS.contains("canvas.captcha"));
        assert!(CanvasCaptchaDetector::JS.contains("#captcha canvas"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"canvas-captcha\"]"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"svg-captcha\"]"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"math-captcha\"]"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"icon-captcha\"]"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"color-captcha\"]"));
        assert!(CanvasCaptchaDetector::JS.contains("[class*=\"rotated-text\"]"));
    }
}