"""真机 人性化拖动 (sendevent 注入) 测试: 350014 是 bot 拖拽模式 还是 号码标记? adb input swipe = 恒速线性 = 最像 bot。sendevent 注入可模拟人手: ease-in-out 加速曲线 + 高斯抖动 + 不规则时间间隔。 若人性化拖动到正确落点 => close(result=1) => 是拖拽模式被检测, 路径=人性化自动化/人工。 仍刷新 => 号码被标记(或人性化仍不够, 需人工确认)。 MT-B 协议 (touchpanel /dev/input/event7): X[0,23040] Y[0,50688], ABS_MT_SLOT=002f TRACKING_ID=0039 X=0035 Y=0036 PRESSURE=0030 TOUCH_MAJOR=0031, BTN_TOUCH=014a BTN_TOOL_FINGER=0145, SYN=0000 """ from __future__ import annotations import subprocess import time import math import random from io import BytesIO import numpy as np from PIL import Image DEV = "/dev/input/event7" SX = 23040 / 1080.0 SY = 50688 / 2376.0 PHOTO_LEFT, PHOTO_RIGHT = 66, 1013 PHOTO_TOP, PHOTO_BOT = 267, 819 HOME_TRUE = 66 + (24 + 61) * (947 / 686.0) OFFSET = 23.0 # v7 收敛到的偏移修正 TRACK_Y = 950 def adb_text(*a): r = subprocess.run(["adb", *a], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60) return r.stdout def cap(): d = subprocess.run(["adb", "exec-out", "screencap", "-p"], capture_output=True, timeout=30).stdout return np.asarray(Image.open(BytesIO(d)).convert("L")) def photo(img): return img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT] def detect_notch(img, x_min_local=230, col_thr=25): reg = photo(img); col = (reg < 90).sum(axis=0); col_r = col[x_min_local:] hot = np.where(col_r > col_thr)[0] if len(hot) == 0: return None br = np.where(np.diff(hot) > 12)[0] segs = [s for s in np.split(hot, br + 1) if len(s) > 10] seg = max(segs, key=lambda s: col_r[s].sum()) segL = x_min_local + int(seg[0]); segR = x_min_local + int(seg[-1]) ys, xs = np.where((reg[:, segL:segR + 1] < 95)) if len(xs) == 0: return None return PHOTO_LEFT + int(round(np.average(xs + segL))) def detect_handle(img, lo=80, hi=340): band = img[932:968, lo:hi]; col = (band < 200).sum(axis=0).astype(float) hot = np.where(col > 12)[0] if len(hot) == 0: return None br = np.where(np.diff(hot) > 6)[0] cands = [s for s in np.split(hot, br + 1) if 10 <= len(s) <= 80] if not cands: return None seg = max(cands, key=lambda s: col[s].max()) return lo + int(round(np.average(seg, weights=col[seg]))) def piece_moved(home, cur): d = np.abs(photo(cur).astype(np.int16) - photo(home).astype(np.int16)) return int((d > 45).sum()) def piece_pos(home, cur): """diff 右簇 cx(拼图块当前位置, screen x)。""" d = np.abs(photo(cur).astype(np.int16) - photo(home).astype(np.int16)) mask = d > 45; col = mask.sum(axis=0); hot = np.where(col > 6)[0] if len(hot) == 0: return None, None w = np.where(col > 6, col, 0); br = np.where(np.diff(hot) > 12)[0] c = [s for s in np.split(hot, br + 1) if len(s) > 12] seg = max(c, key=lambda s: s[-1]) return (PHOTO_LEFT + int(round(np.average(seg, weights=w[seg])))), (PHOTO_LEFT + int(seg[-1])) def top_activity(): out = adb_text("shell", "dumpsys", "activity", "activities") return next((l.strip() for l in out.splitlines() if "topResumedActivity" in l), "(none)") def gen_human_path(x0, y0, x1, y1, n=56, total_s=1.7, seed=1): """ease-in-out + 抖动 + 不规则 dt 的人手路径。返回 [(screen_x,screen_y,sleep_ms)]。""" rnd = random.Random(seed) pts = [] t = 0.0 # 时间间隔: 主体均匀 + 抖动, 末端略慢(减速) for i in range(n): s = i / (n - 1) # smoothstep ease-in-out e = s * s * (3 - 2 * s) x = x0 + (x1 - x0) * e y = y0 + (y1 - y0) * e # 抖动: 平行 ±2px, 垂直 ±1.5px (末端减小) jx = rnd.gauss(0, 2.0) * (1 - 0.6 * s) jy = rnd.gauss(0, 1.2) * (1 - 0.6 * s) x += jx; y += jy # dt: 基础 + 不规则, 起步快末端慢 base = total_s / n * 1000 dt = base * (0.7 + 0.6 * s + rnd.gauss(0, 0.12)) pts.append((x, y, max(8, dt))) # 末点精确到目标 pts[-1] = (x1, y1, 30) return pts def build_script(pts): """生成 input motionevent 人性化拖拽脚本(屏幕坐标, 无需缩放; 有权限)。""" L = ["#!/system/bin/sh"] x0, y0, _ = pts[0] L.append(f"input motionevent DOWN {int(round(x0))} {int(round(y0))}") L.append("sleep 0.04") for (x, y, dt) in pts[1:]: L.append(f"input motionevent MOVE {int(round(x))} {int(round(y))}") L.append(f"sleep {dt/1000.0:.3f}") xn, yn, _ = pts[-1] L.append(f"input motionevent UP {int(round(xn))} {int(round(yn))}") return "\n".join(L) def main(): home = cap() notch = detect_notch(home) hh = detect_handle(home) print(f"[init] notch={notch} handle={hh}", flush=True) if notch is None or hh is None: print("[!] 检测失败"); return 2 target = hh + (notch - HOME_TRUE) + OFFSET print(f"[plan] humanized drag {hh},{TRACK_Y} -> {target:.0f},{TRACK_Y}", flush=True) pts = gen_human_path(hh, TRACK_Y, target, TRACK_Y) script = build_script(pts) import os local = os.path.join(os.path.dirname(__file__), "hdrag.sh") with open(local, "w", newline="\n") as f: f.write(script) subprocess.run(["adb", "push", local, "/sdcard/hdrag.sh"], capture_output=True, timeout=30) adb_text("logcat", "-c") # 后台跑脚本, 多帧跟踪 piece 是否到达缺口 sp = subprocess.Popen(["adb", "shell", "sh", "/sdcard/hdrag.sh"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) reached_cx = 0; reached_r = 0 for k in range(7): time.sleep(0.42) c = cap() cx, r = piece_pos(home, c) if r and r > reached_r: reached_cx = cx or 0; reached_r = r print(f" [t~{0.42*(k+1):.1f}s] piece_cx={cx} piece_right={r}", flush=True) sp.wait() print(f"[max] piece_cx={reached_cx} piece_right={reached_r} (notch={notch}, " f"want_right~{notch+84})", flush=True) time.sleep(4.0) aft = cap() after = top_activity() closed = "KwaiWebViewActivity" not in after changed = not np.array_equal(photo(home), photo(aft)) print(f"[post] closed={closed} photo_changed={changed} top={after[:60]}", flush=True) near = reached_r >= notch + 84 - 30 # 落点接近缺口右缘(±30) if closed: print("\n>>> VERDICT: PASS (result=1)! 人性化拖动过验证。", flush=True) print(">>> 350014 是 bot 拖拽模式被检测(非号码标记)。可行路径 = 人性化 motionevent/人工。", flush=True) elif changed: print("\n>>> 刷新(verify 触发但未过)。落点正确(near) => 号码被标记; 落点偏 => 错位。", flush=True) print(">>> 下一步: 请人工手指滑一次(决定性判据)确认号码是否标记。", flush=True) elif near: print("\n>>> 落点接近缺口却静默回弹(no verify) => bot 拖拽模式仍被检测。需更人性或人工。", flush=True) else: print(f"\n>>> 落点未到缺口(max_right={reached_r} < {notch+84}) => 欠滑, 增大 target。", flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())