"""真机 adb 滑块求解 + 广撒 logcat 抓 result code。 决策逻辑(无需 result code 也可判): - 活动从 KwaiWebViewActivity 变成别的 => result=1, 指纹过(真机路径可行) - md5 变 + 活动仍是 KwaiWebViewActivity => fail(350002 缺口错 或 350014 指纹) * 此时若我确信落点居中(Δ 居中于缺口) => 350014(号码被永久标记) 落点: Δ = hole_center_x - piece_home_center_x piece_home_center_x = photo_left + (disX + cutPicW/2)*scale (disX=24, cutPicW=122) hole: photo 区 <95 暗簇的加权质心(缺口纯黑核心)。 handle: 轨道带内最密簇(滑块按钮), 仅作 swipe 起点 x1(Δ 与 x1 无关, 只要落在按钮上)。 """ from __future__ import annotations import subprocess import sys import time from pathlib import Path import numpy as np from PIL import Image ROOT = Path(__file__).resolve().parent.parent OUT = ROOT / "out" SCALE = 947 / 686 # photo native->screen PHOTO_LEFT, PHOTO_RIGHT = 66, 1013 PHOTO_TOP, PHOTO_BOT = 267, 819 DIS_X, CUT_W = 24, 122 PIECE_HOME_CX = PHOTO_LEFT + (DIS_X + CUT_W / 2) * SCALE # =183 (常量, 布局不变) def adb(*args, binary=False): r = subprocess.run(["adb", *args], capture_output=True, text=not binary, timeout=60) return r.stdout if not binary else r.stdout def grab(path: Path): data = adb("exec-out", "screencap", "-p", binary=True) path.write_bytes(data) return np.asarray(Image.open(path).convert("L")) def top_activity(): out = adb("shell", "dumpsys", "activity", "activities") for ln in out.splitlines(): if "topResumedActivity" in ln: return ln.strip() return "(none)" def detect_hole(img) -> tuple[int, int, int]: """返回 (hole_center_x, cluster_left, cluster_right)。photo 区 <95 暗簇加权质心。""" reg = img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT] dark = (reg < 95) col = dark.sum(axis=0) # 每列暗像素数 hot = np.where(col > 25)[0] # 缺口列(暗像素密集) if len(hot) == 0: raise RuntimeError("no dark cluster (hole) found in photo") # 切分成连续段, 取最宽那段(缺口), 再取其加权质心 breaks = np.where(np.diff(hot) > 5)[0] segs = np.split(hot, breaks + 1) seg = max(segs, key=len) w = col[seg] cx_local = int(round(np.average(seg, weights=w))) cx = PHOTO_LEFT + cx_local return cx, PHOTO_LEFT + int(seg[0]), PHOTO_LEFT + int(seg[-1]) def detect_handle(img) -> tuple[int, int]: """轨道带 y[920,980] 内最密簇(滑块按钮) -> (handle_cx, track_y)。""" band = img[920:980, :] nonwhite = (band < 235) col = nonwhite.sum(axis=0) hot = np.where(col > 20)[0] if len(hot) == 0: return 140, 950 breaks = np.where(np.diff(hot) > 8)[0] segs = np.split(hot, breaks + 1) # 滑块在最左; 取最左且较密的那段(x<300 范围内最密) cand = [s for s in segs if s[0] < 320] seg = max(cand, key=lambda s: col[s].sum()) if cand else segs[0] hx = int(round(np.average(seg, weights=col[seg]))) return hx, 950 def main(): shot = OUT / "swipe_in.png" img = grab(shot) hole_cx, hl, hr = detect_hole(img) handle_x, ty = detect_handle(img) delta = int(round(hole_cx - PIECE_HOME_CX)) x1, y1 = handle_x, ty x2, y2 = handle_x + delta, ty print(f"[det] hole_cx={hole_cx} cluster=[{hl},{hr}] handle_x={handle_x} " f"track_y={ty} piece_home_cx={PIECE_HOME_CX:.0f} => Δ={delta}", flush=True) print(f"[det] swipe ({x1},{y1}) -> ({x2},{y2}) piece lands center @ {PIECE_HOME_CX+delta:.0f} " f"(hole={hole_cx})", flush=True) before_md5 = subprocess.run( ["certutil", "-hashfile", str(shot), "MD5"], capture_output=True, text=True).stdout before = top_activity() print(f"[pre] activity={before[:80]}", flush=True) adb("logcat", "-c") print(f"[act] adb input swipe {x1} {y1} {x2} {y2} 700", flush=True) subprocess.run(["adb", "shell", "input", "swipe", str(x1), str(y1), str(x2), str(y2), "700"], timeout=30) time.sleep(4.0) after = top_activity() aft = OUT / "swipe_out.png" img2 = grab(aft) changed = not np.array_equal(img, img2) same_photo = bool(np.array_equal(img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT], img2[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT])) print(f"[post] activity={after[:80]}", flush=True) print(f"[post] screen_changed={changed} same_photo={same_photo}", flush=True) # 判定 closed = "KwaiWebViewActivity" not in after if closed: print("\n>>> VERDICT: ACTIVITY CLOSED => result=1, 真机指纹过 350014! 真机 WebView 路径可行。", flush=True) elif changed and not same_photo: print("\n>>> VERDICT: 图片刷新(新验证码) + 活动仍在 => FAIL。落点居中(Δ 居中缺口) => 350014(号码被标记)。", flush=True) elif changed and same_photo: print("\n>>> VERDICT: 仅拼图块位移, 图未刷新 => 滑动未完成/未触发 verify。", flush=True) else: print("\n>>> VERDICT: 无变化 => 滑块未动(handle 没抓到)。", flush=True) # 广撒 logcat print("\n===== LOGCAT (captcha/verify/result/350/http) =====", flush=True) lc = adb("logcat", "-d") for ln in lc.splitlines(): low = ln.lower() if any(k in low for k in ("captcha", "verify", "350014", "350002", "35000", "ksecretapi", "result", "okhttp", "--> ", "<-- ")): print(ln[:200], flush=True) if __name__ == "__main__": raise SystemExit(main())