"""v7: 迭代收敛落点。每次 swipe 测 piece 最大位置 => 反馈调整 target => 找 close(PASS)。 R=1.0 (1:1), home_true=183。每次: target = handle_home + (notch_center - 183) + offset 慢 swipe, 轨迹测 max piece_right (落点右缘, 比 cx 稳定) 期望 piece_right = notch_center + 84 (拼图块居中缺口时的右缘) err = (notch_center+84) - max_piece_right ; offset += err 观察: close => result=1 (PASS! 真机指纹过, 号码未标记 => 路径=真机/模拟器) refresh 且 |err|<=7 => 正确落点仍刷新 => 350014 (号码被标记/指纹被卡, 真机也过不去) refresh 且 |err|>7 => 350002 (错位, 继续收敛) 最多 7 次。 """ from __future__ import annotations import subprocess 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 HOME_TRUE = 66 + (24 + 61) * (947 / 686.0) # 183 R = 1.0 TRACK_Y = 950 PIECE_HALF = 84 # cutPic 122 * 1.379 / 2 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, 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, None center = PHOTO_LEFT + int(round(np.average(xs + segL))) return center, PHOTO_LEFT + int(segR) # center, right_edge 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_blob(home, cur): 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, 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] if not c: return None, None, None seg = max(c, key=lambda s: s[-1]) cx = PHOTO_LEFT + int(round(np.average(seg, weights=w[seg]))) return cx, PHOTO_LEFT + int(seg[0]), 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 attempt(home, notch_c, offset): hh = detect_handle(home) if hh is None or notch_c is None: return None target = int(round(hh + (notch_c - HOME_TRUE) / R + offset)) desired_right = notch_c + PIECE_HALF p = subprocess.Popen(["adb", "shell", "input", "swipe", str(hh), str(TRACK_Y), str(target), str(TRACK_Y), "2600"]) max_right = 0; max_cx = 0 for _ in range(7): time.sleep(0.30) c = cap() cx, bl, br = piece_blob(home, c) if br and br > max_right: max_right = br; max_cx = cx p.wait() time.sleep(3.5) aft = cap() after = top_activity() closed = "KwaiWebViewActivity" not in after changed = not np.array_equal(photo(home), photo(aft)) err = desired_right - max_right print(f" target={target} max_piece_right={max_right} (want {desired_right}) " f"err={err:+d} => closed={closed} changed={changed}", flush=True) return dict(closed=closed, changed=changed, err=err, max_cx=max_cx, aft=aft) def main(): offset = 0.0 converged_refresh = False for k in range(7): home = cap() notch_c, notch_r = detect_notch(home) print(f"[try {k}] notch_center={notch_c} notch_right={notch_r} offset={offset:+.0f}", flush=True) if notch_c is None: print(" [!] 无缺口, 等待"); time.sleep(2); continue r = attempt(home, notch_c, offset) if r is None: continue if r["closed"]: print(f"\n>>> VERDICT: PASS (result=1) on try {k}! 真机 WebView 指纹过验证。", flush=True) print(">>> 号码未标记 + 真机指纹OK => 可行路径 = 真机/模拟器 WebView。", flush=True) return 0 if not r["changed"]: # 静默回弹 = 落点太远(错位大), offset 调整幅度加大 print(" (静默回弹: 落点偏离过大, 增大调整)", flush=True) offset += 25 if offset >= 0 else -25 continue # 刷新 => verify 触发 if abs(r["err"]) <= 7: converged_refresh = True print(f" *** 正确落点(err={r['err']:+d}≤7) 却刷新 => 350014 候选 ***", flush=True) # 再确认一次: 微调看是否仍刷新 offset += r["err"] continue offset += r["err"] # 收敛 print("\n>>> 7 次未 PASS。", flush=True) if converged_refresh: print(">>> VERDICT: 正确落点(err≤7)仍刷新 => 350014 (号码被永久标记或真机指纹仍被卡)。", flush=True) print(">>> 真机也过不去 => 纯算 + 真机 WebView 都不行, 只能换号(新号不触发验证码)。", flush=True) else: print(">>> VERDICT: 未收敛到正确落点(每次都错位>7)。需更多迭代或重新标定。", flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())