159 lines
5.9 KiB
Python
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())
|