use chromiumoxide::cdp::browser_protocol::input::{
DispatchKeyEventParams, DispatchKeyEventType, DispatchMouseEventParams, DispatchMouseEventType,
MouseButton,
};
use chromiumoxide::Page;
use rand::{Rng, SeedableRng};
use std::time::Duration;
pub async fn mouse_move_bezier(
page: &Page,
x0: f64,
y0: f64,
x1: f64,
y1: f64,
) -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::from_entropy();
let steps = rng.gen_range(8..=20);
let cx1 = x0 + (x1 - x0) * rng.gen_range(0.2..0.5) + rng.gen_range(-30.0..30.0);
let cy1 = y0 + (y1 - y0) * rng.gen_range(0.1..0.4) + rng.gen_range(-20.0..20.0);
let cx2 = x0 + (x1 - x0) * rng.gen_range(0.6..0.9) + rng.gen_range(-20.0..20.0);
let cy2 = y0 + (y1 - y0) * rng.gen_range(0.5..0.8) + rng.gen_range(-15.0..15.0);
for i in 0..=steps {
let t = i as f64 / steps as f64;
let u = 1.0 - t;
let x = u * u * u * x0 + 3.0 * u * u * t * cx1 + 3.0 * u * t * t * cx2 + t * t * t * x1;
let y = u * u * u * y0 + 3.0 * u * u * t * cy1 + 3.0 * u * t * t * cy2 + t * t * t * y1;
let params = DispatchMouseEventParams::builder()
.r#type(DispatchMouseEventType::MouseMoved)
.x(x)
.y(y)
.build()
.map_err(anyhow::Error::msg)?;
page.execute(params).await?;
let ease = (std::f64::consts::PI * t).sin(); let base_ms = rng.gen_range(8..25);
let delay_ms = base_ms + ((1.0 - ease) * 15.0) as u64;
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
Ok(())
}
pub async fn click_realistic(page: &Page, x: f64, y: f64) -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::from_entropy();
let down = DispatchMouseEventParams::builder()
.r#type(DispatchMouseEventType::MousePressed)
.x(x)
.y(y)
.button(MouseButton::Left)
.click_count(1)
.build()
.map_err(anyhow::Error::msg)?;
page.execute(down).await?;
tokio::time::sleep(Duration::from_millis(rng.gen_range(50..150))).await;
let up = DispatchMouseEventParams::builder()
.r#type(DispatchMouseEventType::MouseReleased)
.x(x)
.y(y)
.button(MouseButton::Left)
.click_count(1)
.build()
.map_err(anyhow::Error::msg)?;
page.execute(up).await?;
Ok(())
}
pub async fn type_realistic(page: &Page, text: &str) -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::from_entropy();
for (i, ch) in text.chars().enumerate() {
let key_text = ch.to_string();
let down = DispatchKeyEventParams::builder()
.r#type(DispatchKeyEventType::KeyDown)
.text(&key_text)
.build()
.map_err(anyhow::Error::msg)?;
page.execute(down).await?;
tokio::time::sleep(Duration::from_millis(rng.gen_range(30..80))).await;
let up = DispatchKeyEventParams::builder()
.r#type(DispatchKeyEventType::KeyUp)
.text(&key_text)
.build()
.map_err(anyhow::Error::msg)?;
page.execute(up).await?;
let base = rng.gen_range(80..250);
let pause = if i > 0 && i % rng.gen_range(4..10) == 0 {
rng.gen_range(200..600) } else {
0
};
tokio::time::sleep(Duration::from_millis(base + pause)).await;
}
Ok(())
}
pub async fn scroll_realistic(
page: &Page,
direction: ScrollDirection,
amount: u32,
) -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::from_entropy();
let steps = rng.gen_range(3..=8).min(amount);
let per_step = (amount as f64 / steps as f64) as i32;
for _ in 0..steps {
let delta = match direction {
ScrollDirection::Down => per_step + rng.gen_range(-20..20),
ScrollDirection::Up => -(per_step + rng.gen_range(-20..20)),
};
let js = format!("window.scrollBy(0, {})", delta);
page.evaluate(js).await?;
tokio::time::sleep(Duration::from_millis(rng.gen_range(100..400))).await;
}
Ok(())
}
pub async fn idle_pause() {
let ms = rand::rngs::StdRng::from_entropy().gen_range(500..2000);
tokio::time::sleep(Duration::from_millis(ms)).await;
}
pub async fn micro_pause() {
let ms = rand::rngs::StdRng::from_entropy().gen_range(100..400);
tokio::time::sleep(Duration::from_millis(ms)).await;
}
#[derive(Debug, Clone, Copy)]
pub enum ScrollDirection {
Up,
Down,
}
#[cfg(test)]
mod tests {
#[test]
fn bezier_control_points_within_range() {
let (x0, _y0) = (100.0, 200.0);
let (x1, _y1) = (500.0, 400.0);
let t0 = 0.0_f64;
let t1 = 1.0_f64;
let u0 = 1.0 - t0;
let u1 = 1.0 - t1;
let bx0 = u0.powi(3) * x0 + t0.powi(3) * x1;
let bx1 = u1.powi(3) * x0 + t1.powi(3) * x1;
assert!((bx0 - x0).abs() < 0.001);
assert!((bx1 - x1).abs() < 0.001);
}
}