"""真机标定 + 精确求解。 轨道(640px)比图片(947px)窄 => handle↔piece 非 1:1, 需标定 ratio。 标定: 慢速 swipe + 帧差(拼图块在静态背景上移动 => 差分干净)。 - diff(ref_home, cap1): 拼图块 home 足迹(左)+ 移动后足迹(右) => home_center - diff(cap1, cap2): 0.7s 内拼图块位移 shift => ratio = shift / (handle位移) 然后刷新后重新检测缺口 => 按 ratio 换算 handle 位移, 精确 swipe。 """ from __future__ import annotations import subprocess import sys import time from io import BytesIO import numpy as np from PIL import Image PHOTO_LEFT, PHOTO_RIGHT = 66, 1013 PHOTO_TOP, PHOTO_BOT = 267, 819 PHOTO_W = PHOTO_RIGHT - PHOTO_LEFT # 947 HANDLE_HOME_X = 114 # 初值, 下面重检测 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_center(img): reg = photo(img) dark = (reg < 95) rowcnt = dark.sum(axis=1) thresh = np.percentile(rowcnt[rowcnt > 30], 60) if (rowcnt > 30).any() else 30 body_rows = np.where(rowcnt >= thresh)[0] if len(body_rows) == 0: return None col = dark[body_rows].sum(axis=0) hot = np.where(col > 15)[0] br = np.where(np.diff(hot) > 10)[0] segs = [s for s in np.split(hot, br + 1) if len(s) > 12] if not segs: return None seg = max(segs, key=lambda s: col[s].sum()) cx = int(round(np.average(seg, weights=col[seg]))) return PHOTO_LEFT + cx, PHOTO_LEFT + int(seg[0]), PHOTO_LEFT + int(seg[-1]) def detect_handle(img): band = img[925:980, :] nw = (band < 235) col = nw.sum(axis=0) hot = np.where(col > 20)[0] if len(hot) == 0: return HANDLE_HOME_X br = np.where(np.diff(hot) > 8)[0] segs = [s for s in np.split(hot, br + 1) if s[0] < 320] if not segs: segs = np.split(hot, br + 1) seg = max(segs, key=lambda s: col[s].sum()) return int(round(np.average(seg, weights=col[seg]))) def detect_track_right(img): band = img[938:962, :] nw = (band < 230) col = nw.sum(axis=0) hot = np.where(col > 8)[0] return int(hot[-1]) if len(hot) else 1000 def diff_blobs(a, b, thr=45): """返回 [(cx_local, seg)] 拼图块足迹簇(局部 x)。""" d = np.abs(b.astype(np.int16) - a.astype(np.int16)) mask = d > thr col = mask.sum(axis=0) hot = np.where(col > 6)[0] if len(hot) == 0: return [] w = np.where(col > 6, col, 0) br = np.where(np.diff(hot) > 12)[0] out = [] for s in np.split(hot, br + 1): if len(s) > 12: out.append((int(round(np.average(s, weights=w[s]))), s)) return out def main(): ref = cap() notchA = detect_notch_center(ref) handle_home = detect_handle(ref) track_right = detect_track_right(ref) print(f"[init] notchA(当前验证码)={notchA} handle_home={handle_home} " f"track_right={track_right}", flush=True) # 慢速 swipe: handle_home -> handle_home+600, 5000ms; 2 个中途帧 DH = 600 x1 = handle_home x2 = handle_home + DH p = subprocess.Popen(["adb", "shell", "input", "swipe", str(x1), str(TRACK_Y), str(x2), str(TRACK_Y), "5000"]) time.sleep(2.2) c1 = cap() t1 = time.monotonic() time.sleep(0.7) c2 = cap() t2 = time.monotonic() p.wait() time.sleep(2.2) # swipe 完成+释放 => verify(错位) => 刷新 # home_center: diff(ref, c1) 的左簇 b1 = diff_blobs(photo(ref), photo(c1)) b1.sort() print(f"[cal] diff(ref,c1) blobs(local cx)= {[c for c, _ in b1]}", flush=True) if len(b1) >= 2: home_local = b1[0][0] moved1_local = b1[-1][0] elif len(b1) == 1: home_local, moved1_local = None, b1[0][0] else: home_local = moved1_local = None home_center = (PHOTO_LEFT + home_local) if home_local else None # ratio: diff(c1,c2) 两个簇位移 / handle 在(t2-t1)位移 dt = t2 - t1 handle_disp_dt = DH * (dt / 5.0) b2 = diff_blobs(photo(c1), photo(c2)) b2.sort() print(f"[cal] diff(c1,c2) blobs(local cx)= {[c for c,_ in b2]} " f"dt={dt:.2f}s handle_disp~{handle_disp_dt:.0f}px", flush=True) ratio = None if len(b2) >= 2: shift = b2[-1][0] - b2[0][0] ratio = shift / handle_disp_dt if handle_disp_dt else None print(f"[cal] c1->c2 piece shift={shift}px => ratio(piece/handle)={ratio:.3f}", flush=True) # 另算 ratio: (moved1 - home)/handle在2.2s位移 if home_local is not None and moved1_local is not None: hd22 = DH * (2.2 / 5.0) ratio2 = (moved1_local - home_local) / hd22 print(f"[cal] cross-check ratio(ref->c1)= {ratio2:.3f} " f"(moved1={moved1_local} home={home_local} hd2.2={hd22:.0f})", flush=True) if ratio is None: ratio = ratio2 print(f"[cal] => home_center={home_center} ratio={ratio}", flush=True) # 刷新后新缺口 fresh = cap() notchB = detect_notch_center(fresh) print(f"[fresh] notchB(刷新后)={notchB}", flush=True) if notchB is None: print("[!] 刷新后未检测到缺口, 退出。", flush=True) return 2 notch_cx = notchB[0] if home_center is None or ratio is None or ratio <= 0: print("[!] 标定失败, 无法精确 swipe。", flush=True) return 2 delta_handle = (notch_cx - home_center) / ratio handle_target = int(round(handle_home + delta_handle)) print(f"[plan] notch_cx={notch_cx} home_center={home_center} ratio={ratio:.3f} " f"=> Δpiece={notch_cx-home_center:.0f} Δhandle={delta_handle:.0f} " f"handle {handle_home}->{handle_target} (track_right={track_right})", flush=True) if handle_target > track_right - 20: print(f"[!] handle_target {handle_target} 超轨道右端 {track_right}!", flush=True) # 执行精确 swipe adb_text("logcat", "-c") print(f"[act] adb input swipe {handle_home} {TRACK_Y} {handle_target} {TRACK_Y} 600", flush=True) subprocess.run(["adb", "shell", "input", "swipe", str(handle_home), str(TRACK_Y), str(handle_target), str(TRACK_Y), "600"], timeout=30) time.sleep(4.0) aft = cap() after_act = adb_text("shell", "dumpsys", "activity", "activities") after_top = next((l.strip() for l in after_act.splitlines() if "topResumedActivity" in l), "(none)") closed = "KwaiWebViewActivity" not in after_top photo_changed = not np.array_equal(photo(fresh), photo(aft)) print(f"[post] top={after_top[:75]}", flush=True) print(f"[post] closed={closed} photo_changed={photo_changed}", flush=True) if closed: print(">>> VERDICT: 活动关闭 => result=1, 真机指纹过 350014! 真机 WebView 路径可行。", flush=True) elif photo_changed: print(">>> VERDICT: 图片刷新+活动仍在 => FAIL。落点精确(已按 ratio 换算居中) => 极可能 350014(号码被标记)。", flush=True) else: print(">>> VERDICT: 图片未变 => 滑动未触发 verify 或未刷新。", flush=True) print("\n===== LOGCAT(net/result) =====", flush=True) lc = adb_text("logcat", "-d") for ln in lc.splitlines(): low = ln.lower() if any(k in low for k in ("captcha", "verify", "3500", "ksecret", "result", "okhttp")): print(ln[:190], flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())