88 lines
3.2 KiB
Python
88 lines
3.2 KiB
Python
"""cloakbrowser-human — Async human-like mouse movement and clicking.
|
|
|
|
Mirrors mouse.py but uses ``await`` for all Playwright calls and
|
|
``async_sleep_ms`` instead of ``sleep_ms``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import random
|
|
from typing import Any, Protocol
|
|
|
|
from .config import HumanConfig, rand, rand_range, rand_int_range, async_sleep_ms
|
|
from .mouse import Point, _ease_in_out, _bezier, _random_control_points, click_target # noqa: reuse pure math
|
|
|
|
|
|
class AsyncRawMouse(Protocol):
|
|
async def move(self, x: float, y: float) -> None: ...
|
|
async def down(self) -> None: ...
|
|
async def up(self) -> None: ...
|
|
async def wheel(self, delta_x: float, delta_y: float) -> None: ...
|
|
|
|
|
|
async def async_human_move(
|
|
raw: AsyncRawMouse,
|
|
start_x: float, start_y: float,
|
|
end_x: float, end_y: float,
|
|
cfg: HumanConfig,
|
|
) -> None:
|
|
dist = math.hypot(end_x - start_x, end_y - start_y)
|
|
if dist < 1:
|
|
return
|
|
|
|
steps = max(cfg.mouse_min_steps, min(cfg.mouse_max_steps, round(dist / cfg.mouse_steps_divisor)))
|
|
start = Point(start_x, start_y)
|
|
end = Point(end_x, end_y)
|
|
cp1, cp2 = _random_control_points(start, end)
|
|
|
|
burst_counter = 0
|
|
burst_size = rand_int_range(cfg.mouse_burst_size)
|
|
|
|
for i in range(steps + 1):
|
|
progress = i / steps
|
|
eased_t = _ease_in_out(progress)
|
|
pt = _bezier(start, cp1, cp2, end, eased_t)
|
|
|
|
wobble_amp = math.sin(math.pi * progress) * cfg.mouse_wobble_max
|
|
wx = pt.x + (random.random() - 0.5) * 2 * wobble_amp
|
|
wy = pt.y + (random.random() - 0.5) * 2 * wobble_amp
|
|
|
|
await raw.move(round(wx), round(wy))
|
|
|
|
burst_counter += 1
|
|
if burst_counter >= burst_size and i < steps:
|
|
await async_sleep_ms(rand_range(cfg.mouse_burst_pause))
|
|
burst_counter = 0
|
|
|
|
if random.random() < cfg.mouse_overshoot_chance:
|
|
overshoot_dist = rand_range(cfg.mouse_overshoot_px)
|
|
angle = math.atan2(end_y - start_y, end_x - start_x)
|
|
await raw.move(round(end_x + math.cos(angle) * overshoot_dist),
|
|
round(end_y + math.sin(angle) * overshoot_dist))
|
|
await async_sleep_ms(rand(30, 70))
|
|
await raw.move(round(end_x + (random.random() - 0.5) * 4),
|
|
round(end_y + (random.random() - 0.5) * 4))
|
|
|
|
|
|
async def async_human_click(raw: AsyncRawMouse, is_input: bool, cfg: HumanConfig) -> None:
|
|
aim_delay = rand_range(cfg.click_aim_delay_input) if is_input else rand_range(cfg.click_aim_delay_button)
|
|
await async_sleep_ms(aim_delay)
|
|
hold_time = rand_range(cfg.click_hold_input) if is_input else rand_range(cfg.click_hold_button)
|
|
await raw.down()
|
|
await async_sleep_ms(hold_time)
|
|
await raw.up()
|
|
|
|
|
|
async def async_human_idle(raw: AsyncRawMouse, seconds: float, cx: float, cy: float, cfg: HumanConfig) -> None:
|
|
import time as _time
|
|
end_time = _time.monotonic() + seconds
|
|
x, y = cx, cy
|
|
while _time.monotonic() < end_time:
|
|
dx = (random.random() - 0.5) * 2 * cfg.idle_drift_px
|
|
dy = (random.random() - 0.5) * 2 * cfg.idle_drift_px
|
|
x += dx
|
|
y += dy
|
|
await raw.move(round(x), round(y))
|
|
await async_sleep_ms(rand_range(cfg.idle_pause_range))
|