stealthscraper_rs/
behavior.rs1#![cfg(feature = "browser")]
2use rand::RngExt;
6use rand_distr::{Distribution, Normal};
7use std::time::Duration;
8
9#[derive(Debug, Clone)]
11pub struct Point {
12 pub x: f64,
14 pub y: f64,
16}
17
18pub fn generate_mouse_path(start: Point, end: Point, num_points: usize) -> Vec<Point> {
20 let mut rng = rand::rng();
21
22 let dx = end.x - start.x;
24 let dy = end.y - start.y;
25 let dist = (dx * dx + dy * dy).sqrt();
26
27 let noise_x = dist * 0.2;
29 let noise_y = dist * 0.2;
30
31 let cp1 = Point {
32 x: start.x + dx * 0.33 + (rng.random::<f64>() - 0.5) * noise_x,
33 y: start.y + dy * 0.33 + (rng.random::<f64>() - 0.5) * noise_y,
34 };
35
36 let cp2 = Point {
37 x: start.x + dx * 0.66 + (rng.random::<f64>() - 0.5) * noise_x,
38 y: start.y + dy * 0.66 + (rng.random::<f64>() - 0.5) * noise_y,
39 };
40
41 let mut path = Vec::with_capacity(num_points);
42 for i in 0..num_points {
43 let t = i as f64 / (num_points - 1) as f64;
44 let t_eased = 1.0 - (1.0 - t).powi(3);
46
47 let u = 1.0 - t_eased;
48 let tt = t_eased * t_eased;
49 let uu = u * u;
50 let uuu = uu * u;
51 let ttt = tt * t_eased;
52
53 let x = uuu * start.x + 3.0 * uu * t_eased * cp1.x + 3.0 * u * tt * cp2.x + ttt * end.x;
54 let y = uuu * start.y + 3.0 * uu * t_eased * cp1.y + 3.0 * u * tt * cp2.y + ttt * end.y;
55
56 path.push(Point { x, y });
57 }
58
59 path
60}
61
62pub fn calculate_typing_delay() -> Duration {
64 let mut rng = rand::rng();
65 let normal = Normal::new(50.0, 15.0).unwrap();
66 let val = normal.sample(&mut rng);
67
68 let base_delay = if val < 20.0 { 20 } else { val as u64 };
69
70 if rng.random_bool(0.05) {
72 let pause = rng.random_range(150..400);
73 Duration::from_millis(base_delay + pause)
74 } else {
75 Duration::from_millis(base_delay)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_generate_mouse_path() {
85 let start = Point { x: 0.0, y: 0.0 };
86 let end = Point { x: 100.0, y: 100.0 };
87 let path = generate_mouse_path(start, end, 50);
88
89 assert_eq!(path.len(), 50);
90
91 let first = path.first().unwrap();
92 assert!((first.x.abs() < 1.0) && (first.y.abs() < 1.0));
93
94 let last = path.last().unwrap();
95 assert!((last.x - 100.0).abs() < 1.0 && (last.y - 100.0).abs() < 1.0);
96 }
97
98 #[test]
99 fn test_calculate_typing_delay() {
100 let delay = calculate_typing_delay();
101 assert!(delay.as_millis() >= 20); }
103
104 #[test]
105 fn test_calculate_typing_delay_long_pause_branch() {
106 let mut hit_long_pause = false;
107 for _ in 0..200 {
109 if calculate_typing_delay().as_millis() >= 170 {
110 hit_long_pause = true;
111 break;
112 }
113 }
114 assert!(hit_long_pause);
115 }
116}