""" 触摸加固层 — 将机器精确操作伪装为真人触摸 功能: 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