163 lines
6.1 KiB
Python
163 lines
6.1 KiB
Python
"""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())
|