214 lines
7.7 KiB
Python
214 lines
7.7 KiB
Python
"""真机标定 + 精确求解。
|
|
|
|
轨道(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())
|