1use async_trait::async_trait;
2use enterpolation::bezier::Bezier;
3use enterpolation::bspline::BSpline;
4use enterpolation::{easing, linear::Linear, Curve};
5use rand::{rng, Rng};
6use std::ops::Div;
7use std::time::Duration;
8use thirtyfour::action_chain::ActionChain;
9use thirtyfour::error::{WebDriverError, WebDriverResult};
10use thirtyfour::{WebDriver, WebElement};
11
12#[derive(Default, Debug, Clone)]
13pub struct MouseAction {
14 interpolation: MouseInterpolation,
15 start_action: MouseButtonAction,
16 end_action: MouseButtonAction,
17 duration: Duration,
18 jitter_amount: i64,
19}
20
21#[derive(Default, Debug, Clone)]
22pub enum MouseButtonAction {
23 #[default]
24 None,
25 LeftClick,
26 LeftHold,
27 LeftRelease,
28 RightClick,
29}
30
31#[derive(Default, Debug, Clone)]
32pub enum MouseInterpolation {
33 #[default]
34 Linear,
35 Spline,
36}
37
38impl MouseAction {
39 pub fn new(
40 interpolation: MouseInterpolation,
41 start_action: MouseButtonAction,
42 end_action: MouseButtonAction,
43 duration: Option<Duration>,
44 jitter_amount: Option<i64>,
45 ) -> Self {
46 let jitter_amount = jitter_amount.unwrap_or(0);
47 let mut duration = duration.unwrap_or(Duration::from_millis(500));
48
49 let divider: u32 = 7;
51 if duration.as_millis() <= divider as u128 {
52 duration = Duration::from_millis(1);
53 } else {
54 duration = duration.div(divider);
55 }
56
57 MouseAction {
58 interpolation,
59 start_action,
60 end_action,
61 duration,
62 jitter_amount,
63 }
64 }
65}
66
67pub enum MouseTarget<'a> {
68 Position { x: f64, y: f64 },
69 WebElement(&'a WebElement),
70}
71
72#[async_trait]
73pub trait MouseActionExt {
74 async fn mouse_action<'a>(
75 &self,
76 action: MouseAction,
77 target_element: MouseTarget<'a>,
78 ) -> WebDriverResult<()>;
79}
80
81#[async_trait]
82impl MouseActionExt for WebDriver {
83 async fn mouse_action<'a>(
87 &self,
88 action: MouseAction,
89 target_element: MouseTarget<'a>,
90 ) -> WebDriverResult<()> {
91 let mouse_x_ret = self
92 .execute(r#"return window.tf_m_mouse_x || -1;"#, Vec::new())
93 .await?;
94 let mut mouse_x = mouse_x_ret.convert::<i64>()?;
95
96 let mouse_y_ret = self
97 .execute(r#"return window.tf_m_mouse_y || -1;"#, Vec::new())
98 .await?;
99 let mut mouse_y = mouse_y_ret.convert::<i64>()?;
100
101 if mouse_x <= -1 || mouse_y <= -1 {
102 self.execute(
103 r#"
104 window.tf_m_mouse_x = window.tf_m_mouse_x || -1;
105 window.tf_m_mouse_y = window.tf_m_mouse_y || -1;
106
107 document.addEventListener("mousemove", (event) => {
108 window.tf_m_mouse_x = event.clientX;
109 window.tf_m_mouse_y = event.clientY;
110 });"#,
111 Vec::new(),
112 )
113 .await?;
114
115 self.action_chain().move_by_offset(1, 1).perform().await?;
116
117 let mouse_x_ret = self
118 .execute(r#"return window.tf_m_mouse_x || -1;"#, Vec::new())
119 .await?;
120 mouse_x = mouse_x_ret.convert::<i64>()?;
121
122 let mouse_y_ret = self
123 .execute(r#"return window.tf_m_mouse_y || -1;"#, Vec::new())
124 .await?;
125 mouse_y = mouse_y_ret.convert::<i64>()?;
126
127 if mouse_x <= -1 || mouse_y <= -1 {
128 return Err(WebDriverError::CommandRecvError(
129 "Failed to get mouse position".to_string(),
130 ));
131 }
132 }
133
134 let (pos_x, pos_y, width, height) = match target_element {
135 MouseTarget::Position { x, y } => (x, y, 0.00, 0.00),
136 MouseTarget::WebElement(we) => {
137 let rect = we.rect().await?;
138
139 (rect.x, rect.y, rect.width, rect.height)
140 }
141 };
142
143 let half_width = div(width, 2.00) as i64;
144 let half_height = div(height, 2.00) as i64;
145 let target_pos_x = pos_x as i64 + half_width; let target_pos_y = pos_y as i64 + half_height; let quarter_width = half_width.checked_div(2).unwrap_or(0);
149 let quarter_height = half_height.checked_div(2).unwrap_or(0);
150 let final_pos_x = target_pos_x + rng().random_range(-quarter_width..=quarter_width);
151 let final_pos_y = target_pos_y + rng().random_range(-quarter_height..=quarter_height);
152
153 let mut positions = match &action.interpolation {
154 MouseInterpolation::Linear => create_linear_steps(
155 mouse_x,
156 mouse_y,
157 final_pos_x,
158 final_pos_y,
159 action.duration.as_millis() as usize,
160 ),
161 MouseInterpolation::Spline => create_spline_steps(
162 mouse_x,
163 mouse_y,
164 final_pos_x,
165 final_pos_y,
166 action.duration.as_millis() as usize,
167 ),
168 };
169
170 if action.jitter_amount > 0 {
171 jitter(&mut positions, action.jitter_amount);
172 }
173
174 let action_chain = self.action_chain_with_delay(None, Some(Duration::ZERO));
175 let mut action_chain = action.start_action.action(action_chain);
176
177 for point in positions {
178 action_chain = action_chain.move_to(point.0, point.1);
179 }
180
181 action.end_action.action(action_chain).perform().await?;
182
183 Ok(())
184 }
185}
186
187impl MouseButtonAction {
188 fn action(&self, action_chain: ActionChain) -> ActionChain {
189 match self {
190 MouseButtonAction::None => action_chain,
191 MouseButtonAction::LeftClick => action_chain.click(),
192 MouseButtonAction::LeftHold => action_chain.click_and_hold(),
193 MouseButtonAction::LeftRelease => action_chain.release(),
194 MouseButtonAction::RightClick => action_chain.context_click(),
195 }
196 }
197}
198
199fn jitter(input: &mut [(i64, i64)], amount: i64) {
200 input.iter_mut().for_each(|(x, y)| {
201 let add_jitter = rng().random_bool(1.00 / 5.00);
202 if add_jitter {
203 *x += rng().random_range(-amount..=amount);
204 *y += rng().random_range(-amount..=amount);
205 }
206 })
207}
208
209fn create_spline_steps(
210 start_x: i64,
211 start_y: i64,
212 end_x: i64,
213 end_y: i64,
214 steps: usize,
215) -> Vec<(i64, i64)> {
216 let x_min = start_x.min(end_x);
217 let x_max = start_x.max(end_x);
218 let y_min = start_y.min(end_y);
219 let y_max = start_y.max(end_y);
220
221 let mut rng = rng();
222 let x_offset_one = rng.random_range(x_min..x_max);
223 let y_offset_one = rng.random_range(y_min..y_max);
224
225 let linear_x = Linear::builder()
226 .elements([start_x as f64, x_offset_one as f64, end_x as f64])
227 .equidistant()
228 .normalized()
229 .easing(easing::Plateau::new(0.00))
230 .build()
231 .unwrap();
232
233 let bezier_y = Bezier::builder()
234 .elements([start_y as f64, y_offset_one as f64, end_y as f64])
235 .normalized::<f64>()
236 .constant::<3>()
237 .build()
238 .unwrap();
239
240 let bspline_y = BSpline::builder()
241 .clamped()
242 .elements([start_y as f64, y_offset_one as f64, end_y as f64])
243 .knots(bezier_y.domain())
244 .dynamic()
245 .build()
246 .unwrap();
247
248 linear_x
249 .take(steps)
250 .zip(bspline_y.take(steps))
251 .map(|(mut x, mut y)| {
252 if x.is_sign_negative() {
253 x = 0.00;
254 }
255 if y.is_sign_negative() {
256 y = 0.00;
257 }
258 (x as i64, y as i64)
259 })
260 .collect::<Vec<_>>()
261}
262
263fn create_linear_steps(
264 start_x: i64,
265 start_y: i64,
266 end_x: i64,
267 end_y: i64,
268 steps: usize,
269) -> Vec<(i64, i64)> {
270 let linear_x = Linear::builder()
271 .elements([start_x as f64, end_x as f64])
272 .equidistant::<f64>()
273 .normalized()
274 .easing(easing::Plateau::new(0.1))
275 .build()
276 .unwrap();
277
278 let linear_y = Linear::builder()
279 .elements([start_y as f64, end_y as f64])
280 .equidistant::<f64>()
281 .normalized()
282 .easing(easing::Plateau::new(0.1))
283 .build()
284 .unwrap();
285
286 linear_x
287 .take(steps)
288 .zip(linear_y.take(steps))
289 .map(|(mut x, mut y)| {
290 if x.is_sign_negative() {
291 x = 0.00;
292 }
293 if y.is_sign_negative() {
294 y = 0.00;
295 }
296 (x as i64, y as i64)
297 })
298 .collect::<Vec<_>>()
299}
300
301fn div(lhs: f64, rhs: f64) -> f64 {
302 if rhs == 0.00 || lhs == 0.00 {
303 return 0.00;
305 }
306 lhs / rhs
307}