"""人性化拖动 v2: 可靠的 input motionevent(>=80ms 间距, 16 点) 到正确落点。 v1 的 56 点 25ms 间距被系统批处理/丢弃 => piece 不动。>=80ms 间距可靠(已验证)。 目标 target = handle + (notch-183) + 23 (v7 收敛的偏移, R=1.0)。 ease-in-out 加速 + 抖动 + 不规则 dt。 判定: close => PASS (人性化过 => 350014 是 bot 拖拽被检测, 非号码标记 => 路径=人性化/人工) refresh & 落点正确 => 350014 (号码标记, 真机人性化也过不去 => 只能换号) refresh & 落点偏 => 350002 (错位) snapback => bot 仍被检测 或 未到缺口 """ from __future__ import annotations import subprocess import time import random import os from io import BytesIO import numpy as np from PIL import Image PHOTO_LEFT, PHOTO_RIGHT = 66, 1013 PHOTO_TOP, PHOTO_BOT = 267, 819 HOME_TRUE = 66 + (24 + 61) * (947 / 686.0) OFFSET = 23.0 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] seg = max(cands, key=lambda s: col[s].max()) return lo + int(round(np.average(seg, weights=col[seg]))) def piece_right(home, cur): d = np.abs(photo(cur).astype(np.int16) - photo(home).astype(np.int16)) col = (d > 45).sum(axis=0); hot = np.where(col > 6)[0] return PHOTO_LEFT + int(hot[-1]) if len(hot) else None 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_path(x0, x1, n=16, base_ms=92, seed=7): rnd = random.Random(seed) pts = [] for i in range(n): s = i / (n - 1) e = s * s * (3 - 2 * s) # smoothstep x = x0 + (x1 - x0) * e + rnd.gauss(0, 1.8) * (1 - 0.5 * s) dt = base_ms * (0.75 + 0.7 * s + rnd.gauss(0, 0.1)) pts.append((int(round(x)), round(dt / 1000.0, 3))) pts[-1] = (int(round(x1)), 0.05) return pts def build(pts): L = ["#!/system/bin/sh", f"input motionevent DOWN {pts[0][0]} {TRACK_Y}", "sleep 0.06"] for x, dt in pts[1:]: L.append(f"input motionevent MOVE {x} {TRACK_Y}") L.append(f"sleep {dt}") L.append(f"input motionevent UP {pts[-1][0]} {TRACK_Y}") return "\n".join(L) def run_once(seed): home = cap() notch = detect_notch(home); hh = detect_handle(home) if notch is None or hh is None: print("[!] detect fail"); return None target = int(round(hh + (notch - HOME_TRUE) + OFFSET)) pts = gen_path(hh, target, seed=seed) local = os.path.join(os.path.dirname(__file__), "hdrag2.sh") with open(local, "w", newline="\n") as f: f.write(build(pts)) subprocess.run(["adb", "push", local, "/sdcard/hdrag2.sh"], capture_output=True, timeout=30) print(f"[run seed={seed}] notch={notch} hh={hh} target={target}", flush=True) sp = subprocess.Popen(["adb", "shell", "sh", "/sdcard/hdrag2.sh"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(0.35) mid = cap() pr = piece_right(home, mid) sp.wait() time.sleep(3.8) aft = cap() after = top_activity() closed = "KwaiWebViewActivity" not in after changed = not np.array_equal(photo(home), photo(aft)) print(f" mid_piece_right={pr} (want~{notch+84}) closed={closed} changed={changed}", flush=True) return dict(notch=notch, pr=pr, closed=closed, changed=changed) def main(): for seed in (7, 13, 21): r = run_once(seed) if r is None: time.sleep(2); continue if r["closed"]: print("\n>>> VERDICT: PASS (result=1)! 人性化拖动过验证。", flush=True) print(">>> 350014 = bot 拖拽被检测(非号码标记)。可行路径=人性化 motionevent / 人工。", flush=True) return 0 time.sleep(1.5) last = r near = last and last["pr"] is not None and last["pr"] >= last["notch"] + 84 - 30 print("\n>>> 3 次人性化拖动均未 PASS。", flush=True) if last and last["changed"] and near: print(">>> VERDICT: 落点正确(near)却刷新 => 350014 (号码被标记, 真机人性化也过不去)。", flush=True) print(">>> 只能换号(新号不触发验证码)。", flush=True) elif last and last["changed"]: print(">>> 刷新但落点偏 => 350002, 人性化落点不稳。", flush=True) elif last and near: print(">>> 落点正确却静默回弹 => bot 拖拽仍被检测(人性化不够)。需人工确认。", flush=True) else: print(f">>> 落点未到缺口(pr={last['pr'] if last else '?'}) 或 motionevent 不稳。", flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())