use crate::{
result::{PebblesError, PebblesErrorDetails},
Service,
};
use rand::Rng;
use serde_json::{json, Value};
use std::rc::Rc;
pub struct Mouse {
x: i64,
y: i64,
service: Rc<Service>,
}
impl Mouse {
pub fn new(service: Rc<Service>) -> Self {
Mouse {
x: 0,
y: 0,
service,
}
}
pub fn down(&self, button: i64) -> Result<(), PebblesError> {
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerDown", "duration": 0, "button": button}
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn up(&self, button: i64) -> Result<(), PebblesError> {
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerUp", "duration": 0, "button": button}
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn click(&self, button: i64) -> Result<(), PebblesError> {
let mut rng = rand::thread_rng();
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerDown", "duration": 0, "button": button},
{"type": "pause", "duration": rng.gen_range(5..200)},
{"type": "pointerUp", "duration": 0, "button": button},
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn move_to(&mut self, x: i64, y: i64, speed: f64) -> Result<(), PebblesError> {
let viewport_width: i64 = match &self
.service
.post(
"/execute/sync",
&json!({"script": "return window.innerWidth;", "args": []}),
)?
.json::<Value>()?["value"]
.as_i64()
{
Some(value) => value.clone() as i64,
None => {
return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
"Could not load page width for mouse movment.",
)))
}
};
let viewport_height: i64 = match &self
.service
.post(
"/execute/sync",
&json!({"script": "return window.innerHeight;", "args": []}),
)?
.json::<Value>()?["value"]
.as_i64()
{
Some(value) => value.clone() as i64,
None => {
return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
"Could not load page height for mouse movment.",
)))
}
};
let wind_fluctuation: f64 = 3.0;
let gravitational_force: f64 = 9.0;
let damped_distance = 12.0;
let mut maximum_step = 25.0;
let sqrt3 = 1.73205080757;
let sqrt5 = 2.2360679775;
let mut currentx = self.x;
let mut currenty = self.y;
let mut velocityx: f64 = 0.0;
let mut velocityy: f64 = 0.0;
let mut wind_forcex: f64 = 0.0;
let mut wind_forcey: f64 = 0.0;
let mut rng = rand::thread_rng();
let mut actions: Value = json!({"actions": [
{"type": "pointer", "id": "mouse", "parameters": {"pointerType": "mouse"}, "actions": []}
]});
let mut distance = (((x - currentx).pow(2) + (y - currenty).pow(2)) as f64).sqrt();
while distance >= 1.0 {
let wind_magnitude = distance.min(wind_fluctuation);
if distance >= damped_distance {
wind_forcex =
wind_forcex / sqrt3 + (rng.gen_range(0.0..2.0) - 1.0) * wind_magnitude / sqrt5;
wind_forcey =
wind_forcey / sqrt3 + (rng.gen_range(0.0..2.0) - 1.0) * wind_magnitude / sqrt5;
} else {
wind_forcex /= sqrt3;
wind_forcey /= sqrt3;
if maximum_step < 3.0 {
maximum_step = rng.gen_range(0.0..3.0) + 3.0;
} else {
maximum_step /= sqrt5;
}
}
velocityx += wind_forcex + gravitational_force * (x - currentx) as f64 / distance;
velocityy += wind_forcey + gravitational_force * (y - currenty) as f64 / distance;
let velocity_magnitude = (velocityx.powi(2) + velocityy.powi(2)).sqrt();
if velocity_magnitude > maximum_step {
let velocity_clip =
maximum_step / 2.0 + rng.gen_range(0.0..1.0) * maximum_step / 2.0;
velocityx = (velocityx / velocity_magnitude) * velocity_clip;
velocityy = (velocityy / velocity_magnitude) * velocity_clip;
}
currentx += velocityx as i64;
currenty += velocityy as i64;
let new_action = vec![json!({
"type": "pointerMove",
"duration": 10,
"x": currentx.clamp(0, viewport_width),
"y": currenty.clamp(0, viewport_height)
})];
if let Some(actions_array) = actions["actions"][0]["actions"].as_array_mut() {
actions_array.extend(new_action);
}
distance = (((x - currentx).pow(2) + (y - currenty).pow(2)) as f64).sqrt();
}
self.service.post("/actions", &actions)?;
self.x = x;
self.y = y;
Ok(())
}
pub fn scroll(&self, distance: f64) -> Result<(), PebblesError> {
fn calculate_acceleration(distance: &f64) -> f64 {
let normalized_distance = (*distance - 3500.0) / 1000.0;
let acceleration = 1.2 + (normalized_distance.tanh() + 1.0) * 0.3;
let random_offset = rand::thread_rng().gen_range(-0.2..0.2);
(acceleration + random_offset).clamp(1.1, 3.0)
}
fn steps_to_target(target: i64, step_size: i64) -> i64 {
let remainder = target % step_size;
if remainder == 0 {
0
} else {
let lower: i64 = target - remainder;
let upper: i64 = target - remainder + step_size;
if (target - lower).abs() <= (target - upper).abs() {
lower / step_size
} else {
upper / step_size
}
}
}
let step_size: i64 = 114;
if distance.abs() < (step_size/2) as f64 {
return Ok(());
}
let multiplier: i64 = if distance > 0.0 { 1 } else { -1 };
let steps: i64 = steps_to_target(distance.abs() as i64, step_size);
let delta: i64 = step_size * multiplier;
println!("{} delta", delta);
let acceleration: f64 = calculate_acceleration(&distance);
let mut duration: i64 = 1000 / (distance.abs() as i64 / step_size);
let mut actions: Value = json!({"actions": [
{"type": "wheel", "id": "wheel", "actions": []}
]});
for _ in 0..steps {
duration = ((duration as f64 / acceleration) as i64).clamp(10, 200);
let new_actions = [
json!({
"type": "scroll",
"x": 0,
"y": 0,
"deltaX": 0,
"deltaY": delta,
"duration": duration,
"origin": "viewport",
}),
json!({
"type": "pause",
"duration": duration / 2,
}),
];
if let Some(actions_array) = actions["actions"][0]["actions"].as_array_mut() {
actions_array.extend(new_actions);
}
}
self.service.post("/actions", &actions)?;
Ok(())
}
}