use anyhow::Result;
use async_trait::async_trait;
use chromiumoxide::Page;
use super::{parse_detection_result, CaptchaInfo, Detector};
pub struct ShadowDomDetector;
impl ShadowDomDetector {
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"));
}
}