ksjsb/tools/phone_human_v2.py
2026-07-30 20:25:56 +08:00

159 lines
5.9 KiB
Python

"""人性化拖动 v2: 可靠的 input motionevent(>=80ms 间距, 16 点) 到正确落点。
v1 的 56 点 25ms 间距被系统批处理/丢弃 => piece 不动。>=80ms 间距可靠(已验证)。
目标 target = handle + (notch-183) + 23 (v7 收敛的偏移, R=1.0)。
ease-in-out 加速 + 抖动 + 不规则 dt。
判定:
close => PASS (人性化过 => 350014 是 bot 拖拽被检测, 非号码标记 => 路径=人性化/人工)
refresh & 落点正确 => 350014 (号码标记, 真机人性化也过不去 => 只能换号)
refresh & 落点偏 => 350002 (错位)
snapback => bot 仍被检测 或 未到缺口
"""
from __future__ import annotations
import subprocess
import time
import random
import os
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)
OFFSET = 23.0
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(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
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
return PHOTO_LEFT + int(round(np.average(xs + segL)))
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]
seg = max(cands, key=lambda s: col[s].max())
return lo + int(round(np.average(seg, weights=col[seg])))
def piece_right(home, cur):
d = np.abs(photo(cur).astype(np.int16) - photo(home).astype(np.int16))
col = (d > 45).sum(axis=0); hot = np.where(col > 6)[0]
return PHOTO_LEFT + int(hot[-1]) if len(hot) else None
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 gen_path(x0, x1, n=16, base_ms=92, seed=7):
rnd = random.Random(seed)
pts = []
for i in range(n):
s = i / (n - 1)
e = s * s * (3 - 2 * s) # smoothstep
x = x0 + (x1 - x0) * e + rnd.gauss(0, 1.8) * (1 - 0.5 * s)
dt = base_ms * (0.75 + 0.7 * s + rnd.gauss(0, 0.1))
pts.append((int(round(x)), round(dt / 1000.0, 3)))
pts[-1] = (int(round(x1)), 0.05)
return pts
def build(pts):
L = ["#!/system/bin/sh",
f"input motionevent DOWN {pts[0][0]} {TRACK_Y}", "sleep 0.06"]
for x, dt in pts[1:]:
L.append(f"input motionevent MOVE {x} {TRACK_Y}")
L.append(f"sleep {dt}")
L.append(f"input motionevent UP {pts[-1][0]} {TRACK_Y}")
return "\n".join(L)
def run_once(seed):
home = cap()
notch = detect_notch(home); hh = detect_handle(home)
if notch is None or hh is None:
print("[!] detect fail"); return None
target = int(round(hh + (notch - HOME_TRUE) + OFFSET))
pts = gen_path(hh, target, seed=seed)
local = os.path.join(os.path.dirname(__file__), "hdrag2.sh")
with open(local, "w", newline="\n") as f:
f.write(build(pts))
subprocess.run(["adb", "push", local, "/sdcard/hdrag2.sh"],
capture_output=True, timeout=30)
print(f"[run seed={seed}] notch={notch} hh={hh} target={target}", flush=True)
sp = subprocess.Popen(["adb", "shell", "sh", "/sdcard/hdrag2.sh"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(0.35)
mid = cap()
pr = piece_right(home, mid)
sp.wait()
time.sleep(3.8)
aft = cap()
after = top_activity()
closed = "KwaiWebViewActivity" not in after
changed = not np.array_equal(photo(home), photo(aft))
print(f" mid_piece_right={pr} (want~{notch+84}) closed={closed} changed={changed}", flush=True)
return dict(notch=notch, pr=pr, closed=closed, changed=changed)
def main():
for seed in (7, 13, 21):
r = run_once(seed)
if r is None:
time.sleep(2); continue
if r["closed"]:
print("\n>>> VERDICT: PASS (result=1)! 人性化拖动过验证。", flush=True)
print(">>> 350014 = bot 拖拽被检测(非号码标记)。可行路径=人性化 motionevent / 人工。", flush=True)
return 0
time.sleep(1.5)
last = r
near = last and last["pr"] is not None and last["pr"] >= last["notch"] + 84 - 30
print("\n>>> 3 次人性化拖动均未 PASS。", flush=True)
if last and last["changed"] and near:
print(">>> VERDICT: 落点正确(near)却刷新 => 350014 (号码被标记, 真机人性化也过不去)。", flush=True)
print(">>> 只能换号(新号不触发验证码)。", flush=True)
elif last and last["changed"]:
print(">>> 刷新但落点偏 => 350002, 人性化落点不稳。", flush=True)
elif last and near:
print(">>> 落点正确却静默回弹 => bot 拖拽仍被检测(人性化不够)。需人工确认。", flush=True)
else:
print(f">>> 落点未到缺口(pr={last['pr'] if last else '?'}) 或 motionevent 不稳。", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())