做了什么:
- 包改为分层: tcaptcha/{protocol,telemetry,solvers,ocr,adapters,models,tests}
* protocol/ client.py transport.py proxy.py
* telemetry/ crypto.py values.py key.py opcodes.py
disasm.py program.py layout.py modules.py
* solvers/ text_click.py icon_click.py icon_click_rotate.py slider.py
* ocr/ engines.py slicer.py
* adapters/ one_ms.py
- tools/ 里原本是**包运行时依赖**的 tdc_{disasm,program,layout,modules}.py 并入 telemetry/
(放在 tools/ 属于分层倒置),tools/ 只留 CLI: tdc_key_pure.py / tdc_profile.py / cdp_*.js
- crypto.py 529 -> 340 行: 切除「成功样本模板回填」与「Node 插桩兜底」两条老路;
generate_telemetry 只剩 python_values 一种模式,传别的会直接报错并说明原因
- 求解器改名分清: solver.py -> solvers/icon_click.py(client 实际在用的 YOLO+ResNet18 ONNX),
icon_click_solver.py -> solvers/icon_click_rotate.py(仅测试在用,留作对照)
- pyproject: 去掉 ultralytics/onnx(训练栈已删),补上真正在用的 onnxruntime
删除了什么:
- 雨云整条线 + dist/rainyun_qinglong(104M 旧快照)
- 模板回填: models/success_collect_template.b64、tdc_worker.js、analyze_collect_layout.py
- 「补环境」时代浏览器 JS: chrome_fetch/manual_captcha/probe_captcha/solve_in_browser/tdc_browser
- 旧 VM 探索工具 10 个(tdc_collectgen/tdc_slotmap/tdc_lift/tdc_vm_run/tdc_query/tdc_probe 等)
+ 8 个基于它们的旧 verify 脚本
- 训练/标注/评估: train_classifier/train_detector/prepare_yolo/web_labeler/collector/
siamese/evaluate/benchmark_verify
- tcaptcha/core_js/(60 个分析产物)、TOOLCHAIN_REPORT.md(整篇在描述已删工具)
- 3 个已无任何引用的 PyTorch 权重:icon_classifier.pt 43M / yolo_detector.pt 5.9M /
siamese_matcher.pt 1.6M —— 线上只跑 onnx,tcaptcha/ 117M -> 66M
为什么:
- 项目后面要专门承载「滑块 / 文字点选 / 图标点选」三种求解器;扁平包 + 运行时依赖混在
tools/ + 两条已废弃的 collect 生成路线,会让后续扩展持续踩坑
怎么验证:
- .venv/bin/python -m tcaptcha.selftest 全绿
- scripts/verify/verify_offline.py 逐槽全等 + 分段几何一致
- scripts/verify/verify_values.py 4 份真机明文 152/152 逐槽全等
- scripts/verify/send_test.py --mode python_values 1ms 签到页文字点选 errorCode=0
- 回滚点: tag backup/pre-refactor-20261012
106 lines
4.2 KiB
Python
106 lines
4.2 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 --mode python_keyed # 对照组:旧模板回填
|
|
"""
|
|
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_TEXT_CLICK, TencentCaptchaClient # noqa: E402
|
|
|
|
AID = "195672344"
|
|
ENTRY = "https://1ms.run/console/personal/benefits/check-in"
|
|
REFERER = "https://1ms.run/"
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description="1ms 签到页文字点选 · 真实发送测试")
|
|
ap.add_argument("--mode", default="python_values",
|
|
choices=["python_values", "python_keyed", "python"])
|
|
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]
|
|
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_TEXT_CLICK,),
|
|
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("耗时 : %.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())
|