use crate::Page;
use anyhow::Result;
use async_trait::async_trait;
use super::{parse_detection_result, CaptchaInfo, Detector};
pub struct HCaptchaDetector;
impl HCaptchaDetector {
pub const JS: &'static str = r#"(function() {
const hcaptcha = document.querySelector('.h-captcha, [data-hcaptcha-sitekey]');
const hcaptchaScript = document.querySelector('script[src*="hcaptcha.com"]');
if (hcaptcha || hcaptchaScript) {
let el = hcaptcha;
if (!el) {
const fallback = document.querySelector('[data-sitekey]');
if (fallback && !fallback.hasAttribute('data-turnstile-sitekey') && !fallback.matches('.g-recaptcha')) {
el = fallback;
}
}
const result = { kind: 'hcaptcha', site_key: null, container: '.h-captcha' };
if (el) {
result.site_key = el.getAttribute('data-sitekey') ||
el.getAttribute('data-hcaptcha-sitekey');
}
return result;
}
return { kind: 'none', site_key: null, container: null };
})()"#;
}
#[async_trait]
impl Detector for HCaptchaDetector {
fn name(&self) -> &'static str {
"hcaptcha"
}
fn priority(&self) -> i32 {
30
}
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 hcaptcha_detector_js_has_selectors() {
assert!(HCaptchaDetector::JS.contains(".h-captcha"));
assert!(HCaptchaDetector::JS.contains("[data-hcaptcha-sitekey]"));
assert!(HCaptchaDetector::JS.contains("hcaptcha.com"));
}
#[test]
fn hcaptcha_detector_excludes_other_provider_sitekeys() {
assert!(HCaptchaDetector::JS.contains("!fallback.hasAttribute('data-turnstile-sitekey')"));
assert!(HCaptchaDetector::JS.contains("!fallback.matches('.g-recaptcha')"));
}
}