use crate::Page;
use anyhow::Result;
use async_trait::async_trait;
use super::{parse_detection_result, CaptchaInfo, Detector};
pub struct TurnstileDetector;
impl TurnstileDetector {
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) {
// Explicit-render mounts (e.g. turnstile.render('#cf-turnstile-mount', ...))
// may not have data-sitekey on a DOM element. Accept the script presence
// as sufficient evidence when paired with a known mount point.
const mount = document.querySelector('#cf-turnstile, #cf-turnstile-mount, [id*="turnstile"]');
const cfEl = document.querySelector('[data-sitekey]');
if (mount || cfEl) {
return {
kind: 'turnstile',
site_key: cfEl ? cfEl.getAttribute('data-sitekey') : null,
container: mount ? ('#' + mount.id) : (cfEl ? '[data-sitekey]' : null)
};
}
// Fallback: script alone is strong evidence of Turnstile
return { kind: 'turnstile', site_key: null, container: null };
}
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"));
assert!(TurnstileDetector::JS.contains("#cf-turnstile"));
assert!(TurnstileDetector::JS.contains("#cf-turnstile-mount"));
assert!(TurnstileDetector::JS.contains("turnstile"));
}
}