Some checks failed
SDK CI / python-compile (push) Has been cancelled
- 新增 sdk/app/agent(Hook/Skills/Anti-ban 等)及 NAS/ADB 脚本 - 更新 Android 端、unified、PHP SDK、Docker Compose - Soul 文档迁移至 Soul调研/;移除资料目录内 APK - .gitignore 排除 sdk/tmp、sdk/logs、sdk/tmp_rom Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
"""
|
||
触摸加固层 — 将机器精确操作伪装为真人触摸
|
||
|
||
功能:
|
||
1. 坐标随机偏移 (+-3~8px)
|
||
2. 点击时长随机化 (50~150ms)
|
||
3. 贝塞尔曲线轨迹滑动 (替代直线滑动)
|
||
4. 按压-微移-抬起时序模拟
|
||
5. 可配置的「手抖」程度
|
||
"""
|
||
|
||
import logging
|
||
import math
|
||
import random
|
||
import time
|
||
from typing import List, Optional, Tuple
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
Point = Tuple[float, float]
|
||
|
||
|
||
class TouchHardener:
|
||
"""触摸事件加固 — 让自动化操作看起来像人"""
|
||
|
||
def __init__(self, device=None, tremor_level: float = 1.0):
|
||
"""
|
||
Args:
|
||
device: uiautomator2 设备对象
|
||
tremor_level: 手抖程度倍数 (0.5=稳手, 1.0=普通, 2.0=抖得厉害)
|
||
"""
|
||
self.d = device
|
||
self.tremor = max(0.1, tremor_level)
|
||
|
||
def humanized_click(self, x: int, y: int) -> Tuple[int, int]:
|
||
"""
|
||
带随机偏移的点击。返回实际点击坐标。
|
||
"""
|
||
offset_x = random.gauss(0, 3 * self.tremor)
|
||
offset_y = random.gauss(0, 3 * self.tremor)
|
||
actual_x = max(0, int(x + offset_x))
|
||
actual_y = max(0, int(y + offset_y))
|
||
|
||
duration_ms = random.randint(50, 150)
|
||
|
||
if self.d:
|
||
self.d.click(actual_x, actual_y)
|
||
|
||
settle_ms = random.uniform(30, 80)
|
||
time.sleep(settle_ms / 1000)
|
||
|
||
logger.debug(f"click ({x},{y}) → ({actual_x},{actual_y}) dur={duration_ms}ms")
|
||
return actual_x, actual_y
|
||
|
||
def humanized_swipe(self, x1: int, y1: int, x2: int, y2: int,
|
||
duration: float = 0.5, steps: int = 0) -> List[Point]:
|
||
"""
|
||
贝塞尔曲线滑动。返回轨迹点序列。
|
||
"""
|
||
sx = x1 + random.gauss(0, 2 * self.tremor)
|
||
sy = y1 + random.gauss(0, 2 * self.tremor)
|
||
ex = x2 + random.gauss(0, 2 * self.tremor)
|
||
ey = y2 + random.gauss(0, 2 * self.tremor)
|
||
|
||
ctrl_points = self._random_bezier_controls(sx, sy, ex, ey)
|
||
|
||
if steps <= 0:
|
||
dist = math.hypot(ex - sx, ey - sy)
|
||
steps = max(8, int(dist / 15))
|
||
|
||
trajectory = self._bezier_curve(sx, sy, ex, ey, ctrl_points, steps)
|
||
|
||
if self.d:
|
||
self.d.swipe(int(sx), int(sy), int(ex), int(ey), duration=duration, steps=steps)
|
||
|
||
logger.debug(f"swipe ({x1},{y1})→({x2},{y2}) pts={len(trajectory)}")
|
||
return trajectory
|
||
|
||
def humanized_long_press(self, x: int, y: int, duration_ms: int = 800):
|
||
"""长按 — 带起始微抖"""
|
||
actual_x = int(x + random.gauss(0, 2 * self.tremor))
|
||
actual_y = int(y + random.gauss(0, 2 * self.tremor))
|
||
actual_dur = duration_ms + random.randint(-100, 150)
|
||
actual_dur = max(300, actual_dur)
|
||
|
||
if self.d:
|
||
self.d.long_click(actual_x, actual_y, duration=actual_dur / 1000)
|
||
|
||
logger.debug(f"long_press ({x},{y})→({actual_x},{actual_y}) dur={actual_dur}ms")
|
||
|
||
def humanized_type(self, text: str, char_delay_range: Tuple[float, float] = (0.03, 0.12)):
|
||
"""
|
||
逐字输入 — 每个字符间隔随机延迟,模拟打字节奏。
|
||
"""
|
||
for i, char in enumerate(text):
|
||
if self.d:
|
||
self.d.send_keys(char)
|
||
delay = random.uniform(*char_delay_range)
|
||
if char in (' ', ',', '.', '。', ','):
|
||
delay *= random.uniform(1.5, 3.0)
|
||
time.sleep(delay)
|
||
logger.debug(f"typed {len(text)} chars")
|
||
|
||
def pre_action_pause(self):
|
||
"""操作前的微停顿 — 模拟人的反应时间"""
|
||
pause = random.uniform(0.2, 0.8) * self.tremor
|
||
time.sleep(pause)
|
||
|
||
def post_action_pause(self):
|
||
"""操作后的短暂停顿 — 模拟人看结果"""
|
||
pause = random.uniform(0.3, 1.2) * self.tremor
|
||
time.sleep(pause)
|
||
|
||
# ---- 内部方法 ----
|
||
|
||
@staticmethod
|
||
def _random_bezier_controls(x1: float, y1: float,
|
||
x2: float, y2: float) -> List[Point]:
|
||
"""生成 1~2 个随机控制点,使路径弯曲"""
|
||
mx = (x1 + x2) / 2
|
||
my = (y1 + y2) / 2
|
||
dist = math.hypot(x2 - x1, y2 - y1)
|
||
spread = dist * random.uniform(0.1, 0.35)
|
||
|
||
c1 = (mx + random.gauss(0, spread), my + random.gauss(0, spread))
|
||
|
||
if random.random() > 0.5:
|
||
c2 = (mx + random.gauss(0, spread * 0.6), my + random.gauss(0, spread * 0.6))
|
||
return [c1, c2]
|
||
return [c1]
|
||
|
||
@staticmethod
|
||
def _bezier_curve(x1: float, y1: float, x2: float, y2: float,
|
||
controls: List[Point], steps: int) -> List[Point]:
|
||
"""计算贝塞尔曲线点"""
|
||
points: List[Point] = []
|
||
if len(controls) == 1:
|
||
cx, cy = controls[0]
|
||
for i in range(steps + 1):
|
||
t = i / steps
|
||
bx = (1 - t) ** 2 * x1 + 2 * (1 - t) * t * cx + t ** 2 * x2
|
||
by = (1 - t) ** 2 * y1 + 2 * (1 - t) * t * cy + t ** 2 * y2
|
||
points.append((bx, by))
|
||
elif len(controls) >= 2:
|
||
c1x, c1y = controls[0]
|
||
c2x, c2y = controls[1]
|
||
for i in range(steps + 1):
|
||
t = i / steps
|
||
bx = ((1 - t) ** 3 * x1 + 3 * (1 - t) ** 2 * t * c1x +
|
||
3 * (1 - t) * t ** 2 * c2x + t ** 3 * x2)
|
||
by = ((1 - t) ** 3 * y1 + 3 * (1 - t) ** 2 * t * c1y +
|
||
3 * (1 - t) * t ** 2 * c2y + t ** 3 * y2)
|
||
points.append((bx, by))
|
||
else:
|
||
for i in range(steps + 1):
|
||
t = i / steps
|
||
points.append((x1 + (x2 - x1) * t, y1 + (y2 - y1) * t))
|
||
return points
|