Files
hello_js_reverse_skill/scripts/verify/send_test.py
T
yml2213 74a863c8b3 feat(icon): 轨迹时序指纹修复 —— 1ms 图标 4/8→7/8=0,errorCode 语义与轨迹对拍收口
- 真因:dt 分布与真机严重不符(旧 50% 落 0~16ms,真机 79% 落 13~20ms)+ 点数
  随机 57~65(真机 6/6 恒 61)。按 6 条真机轨迹重拟合 _sample_dt,新增 _fit_points
  钉死 61 点(补齐用 [0,0,dt] 驻留点,不能复制位移点——净位移会被放大)。
- A/B:1ms 图标 0×4→0×7(前 7 连 0);残余偶发 12 为连发频控形态,低频使用即可。
- errorCode 语义标定:50=答案被否(容差实测 25~40px,我们点偏仅 2~3px),
  12=包级拒答;判点位只看 50/0。
- 顺带修 build_plain:轨迹落末槽(k=37)时 chunk2 恒补前导逗号 → 非法 JSON。
- 新增 scripts/verify/send_campaign.py(批量真发+落图+统计)与 judge_icons.py
  (提示目标 vs 点击点并排判读);账本 #64~#74 + 专文 ICON2408.md。

复验:selftest 全绿;判读图 24/24 点位全对;mliev 图标 9/10=0。
2026-10-12 14:32:14 +08:00

126 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
"""send_test.py —— 真实发送测试:1ms 签到页的**文字点选**。
只打这一条链路(用户指定):
aid 195672344 (1ms.run)
入口 https://1ms.run/console/personal/benefits/check-in
题型 2404 文字点选(prehandle 会混发滑块/图标,脚本按 accept_kinds 刷新到文字点选为止)
遥测 --mode python_values(默认)= tcaptcha/tdc_values.py 的**纯离线**生成
脚本会 hook 客户端的 generate_telemetry,把这一颗 collect 的段几何打出来 ——
用来证明"发出去的确实是新链路生成的包",而不是旧模板回填。
用法:
.venv/bin/python scripts/verify/send_test.py
.venv/bin/python scripts/verify/send_test.py --kind icon # 2408 图标点选
.venv/bin/python scripts/verify/send_test.py --kind slider # 2401/2402 滑块
注: `--mode` 只剩 `python_values` 一种 —— 模板回填 / Node 兜底两条老路已整体删除
(它们产生的 collect 与本次会话构建不同源,在真实 aid 上必然 errorCode=12)。
"""
import argparse
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tools"))
import tcaptcha.protocol.client as client_mod # noqa: E402
from tcaptcha.protocol.client import (KIND_ICON_CLICK, KIND_SLIDER, # noqa: E402
KIND_TEXT_CLICK, TencentCaptchaClient)
AID = "195672344"
ENTRY = "https://1ms.run/console/personal/benefits/check-in"
REFERER = "https://1ms.run/"
KIND_MAP = {
"text": (KIND_TEXT_CLICK,),
"slider": (KIND_SLIDER,),
"icon": (KIND_ICON_CLICK,),
"any": (KIND_TEXT_CLICK, KIND_ICON_CLICK, KIND_SLIDER),
}
def main() -> int:
ap = argparse.ArgumentParser(description="1ms 签到页文字点选 · 真实发送测试")
ap.add_argument("--mode", default="python_values", choices=["python_values"],
help="遥测生成方式(只有 python_values:纯离线按本次会话构建生成)")
ap.add_argument("--kind", default="text", choices=list(KIND_MAP),
help="只解哪种题(默认 text=文字点选)")
ap.add_argument("--max-refreshes", type=int, default=12)
ap.add_argument("--max-retries", type=int, default=2)
args = ap.parse_args()
seen = {}
real_gen = client_mod.generate_telemetry
def hooked(tdc_code, points, **kw):
t0 = time.time()
out = real_gen(tdc_code, points, **kw)
seen.update({
"source": out.get("source"),
"collect_len": len(out.get("collect") or ""),
"eks_len": len(out.get("eks") or ""),
"gen_ms": int((time.time() - t0) * 1000),
"points": [list(p) for p in points],
})
# 段几何(新链路才有;旧模板回填没有 segments)
if out.get("source") == "python_values":
try:
from tcaptcha.telemetry.crypto import split_collect_segments, xtea_decrypt_bytes
import base64
plain = xtea_decrypt_bytes(base64.b64decode(out["collect"]),
out["key"]).decode("latin1").rstrip("\x00")
c1, traj, c2, sd = split_collect_segments(plain)
seen["segments"] = [len(c1), len(traj), len(c2), len(sd)]
seen["traj_points"] = len(json.loads(traj))
seen["traj_tail"] = json.loads(traj)[-1]
except Exception as e: # noqa: BLE001
seen["segments_err"] = str(e)[:120]
try: # 失败时落盘,便于事后解剖
open("/tmp/send_test_fail.b64", "w").write(out["collect"])
open("/tmp/send_test_fail.txt", "w", encoding="latin1").write(plain)
except Exception: # noqa: BLE001
pass
print("[hook] %s" % json.dumps(seen, ensure_ascii=False))
return out
client_mod.generate_telemetry = hooked
c = TencentCaptchaClient(
aid=AID, entry_url=ENTRY, referer=REFERER, wb="1",
accept_kinds=KIND_MAP[args.kind],
telemetry_mode=args.mode,
proxy=None,
)
t0 = time.time()
try:
res = c.solve(max_retries=args.max_retries, max_refreshes=args.max_refreshes)
finally:
c.close()
print()
print("=" * 70)
print("模式 : %s" % args.mode)
print("题型 : %s" % args.kind)
print("耗时 : %.1fs" % (time.time() - t0))
print("success : %s" % res.get("success"))
print("kind : %s 指令=%r" % (res.get("kind"), res.get("instruction")))
print("points : %s" % res.get("points"))
print("ticket : %r" % res.get("ticket"))
print("randstr : %r" % res.get("randstr"))
print("errorCode : %s" % (res.get("raw_response") or {}).get("errorCode"))
print("原始响应 : %s" % json.dumps(res.get("raw_response") or {},
ensure_ascii=False)[:400])
print("本颗遥测 : %s" % json.dumps(seen, ensure_ascii=False))
print("error : %s" % (res.get("error") or "")[:200])
print("=" * 70)
return 0
if __name__ == "__main__":
raise SystemExit(main())