use std::time::{Duration, Instant};
use serde_json::Value;
use tokio::time::sleep;
use crate::Result;
use crate::cdp::ChromiumTab;
const PROBE_JS: &str = r#"(function () {
var t = (document.title || '').toLowerCase();
var titleChallenge = t.indexOf('just a moment') >= 0
|| t.indexOf('attention required') >= 0
|| t.indexOf('checking your browser') >= 0
|| t.indexOf('verifying') >= 0
|| t.indexOf('请稍候') >= 0;
var optChallenge = (typeof window._cf_chl_opt !== 'undefined');
var box = null;
var ifr = document.querySelector(
'iframe[src*="challenges.cloudflare.com"], iframe[id^="cf-chl-widget"], iframe[title*="Cloudflare"], iframe[title*="challenge"], iframe[title*="验证"]'
);
if (ifr) {
var r = ifr.getBoundingClientRect();
if (r.width > 0 && r.height > 0) {
box = { x: r.x, y: r.y, w: r.width, h: r.height };
}
}
var challenge = titleChallenge || optChallenge || box !== null;
return JSON.stringify({ challenge: challenge, box: box, title: document.title });
})()"#;
impl ChromiumTab {
pub async fn pass_cloudflare_default(&self) -> Result<bool> {
self.pass_cloudflare(Duration::from_secs(30)).await
}
pub async fn pass_cloudflare(&self, timeout: Duration) -> Result<bool> {
let deadline = Instant::now() + timeout;
let mut clicked = 0u32;
loop {
let (challenge, checkbox) = self.cf_state().await?;
if !challenge {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
match checkbox {
Some((cx, cy)) => {
tracing::debug!(x = cx, y = cy, "点击 Cloudflare Turnstile 复选框(CDP)");
self.trusted_click(cx, cy).await?;
clicked += 1;
sleep(Duration::from_millis(2500)).await;
}
None => {
sleep(Duration::from_millis(1000)).await;
}
}
if clicked > 8 {
sleep(Duration::from_millis(500)).await;
}
}
}
pub async fn is_cloudflare(&self) -> Result<bool> {
Ok(self.cf_state().await?.0)
}
async fn cf_state(&self) -> Result<(bool, Option<(f64, f64)>)> {
let probe = parse_probe(self.run_js(PROBE_JS).await?);
let challenge = probe
.get("challenge")
.and_then(Value::as_bool)
.unwrap_or(false);
let checkbox = probe.get("box").filter(|b| b.is_object()).map(|b| {
let x = b.get("x").and_then(Value::as_f64).unwrap_or(0.0);
let y = b.get("y").and_then(Value::as_f64).unwrap_or(0.0);
let w = b.get("w").and_then(Value::as_f64).unwrap_or(0.0);
let h = b.get("h").and_then(Value::as_f64).unwrap_or(0.0);
let cx = x + 30.0_f64.min(w / 2.0).max(8.0);
let cy = y + h / 2.0;
(cx, cy)
});
Ok((challenge, checkbox))
}
async fn trusted_click(&self, x: f64, y: f64) -> Result<()> {
self.mouse_move(x - 8.0, y - 5.0).await?;
sleep(Duration::from_millis(90)).await;
self.mouse_move(x - 2.0, y + 1.0).await?;
sleep(Duration::from_millis(70)).await;
self.mouse_move(x, y).await?;
sleep(Duration::from_millis(130)).await;
self.mouse_down(x, y).await?;
sleep(Duration::from_millis(60)).await;
self.mouse_up(x, y).await?;
Ok(())
}
}
fn parse_probe(v: Value) -> Value {
match v.as_str() {
Some(s) => serde_json::from_str(s).unwrap_or(Value::Null),
None => v,
}
}