Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-verify-patcher/scripts/inspect_assets.py

120 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""检查第二阶段验证码验证所需的离线证据是否齐备。"""
from __future__ import annotations
import argparse
import imghdr
import json
import sys
import wave
from pathlib import Path
from typing import Any
def configure_utf8_stdio() -> None:
for stream in (sys.stdout, sys.stderr):
if hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8")
configure_utf8_stdio()
REQUIRED_BY_TYPE = {
"text": ["image"],
"math": ["image_or_text"],
"slider": ["background_or_screenshot", "track_width"],
"click-select": ["screenshot", "prompt"],
"rotate": ["image", "track_width"],
"grid": ["screenshot", "prompt"],
"audio": ["audio"],
"drag-drop": ["screenshot", "source_target_bounds"],
"trace-draw": ["screenshot_or_path_points"],
"scratch": ["box"],
"image-restore": ["image_or_tiles"],
"area-select": ["screenshot", "prompt"],
"difference-click": ["image_pair_or_screenshot"],
"font-identify": ["screenshot", "prompt"],
"semantic-reasoning": ["screenshot", "prompt"],
"game-challenge": ["screenshot", "prompt_or_provider_key"],
"pow-challenge": ["challenge_payload"],
"risk-score": ["sitekey", "action_or_server_log"],
"one-click": ["component_html"],
"multi-step": ["round_screenshots"],
"qa-logic": ["question"],
"token-widget": ["sitekey", "pageurl"],
"waf-challenge": ["headers_or_challenge_html"],
}
def inspect_file(path: Path) -> dict[str, Any]:
item: dict[str, Any] = {
"path": str(path),
"exists": path.exists(),
"is_file": path.is_file() if path.exists() else False,
}
if not item["is_file"]:
return item
item["size_bytes"] = path.stat().st_size
image_type = imghdr.what(path)
if image_type:
item["kind"] = f"image/{image_type}"
elif path.suffix.lower() in {".mp3", ".ogg", ".m4a", ".flac"}:
item["kind"] = f"audio/{path.suffix.lower().lstrip('.')}"
elif path.suffix.lower() == ".wav":
item["kind"] = "audio/wav"
try:
with wave.open(str(path), "rb") as handle:
item["audio"] = {
"channels": handle.getnchannels(),
"sample_width": handle.getsampwidth(),
"frame_rate": handle.getframerate(),
"frames": handle.getnframes(),
}
except wave.Error:
item["audio_error"] = "无法读取 wav 元信息"
else:
item["kind"] = "text-or-binary"
return item
def build_report(args: argparse.Namespace) -> dict[str, Any]:
files = [inspect_file(Path(value)) for value in args.file or []]
provided = set(args.provided or [])
captcha_type = args.captcha_type
required = REQUIRED_BY_TYPE.get(captcha_type, [])
missing = [name for name in required if name not in provided]
return {
"captcha_type": captcha_type,
"required_evidence": required,
"provided_evidence": sorted(provided),
"missing_evidence": missing,
"files": files,
"ready_for_offline_flow": not missing and all(item.get("exists") for item in files),
"notes": [
"只检查离线证据齐备性,不打开网页、不读取 Cookie/Storage、不提交验证。",
"如果要真实网页执行,必须按 verification-workflow.md 再次确认。",
],
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="检查验证码验证流程所需证据")
parser.add_argument("--captcha-type", required=True)
parser.add_argument("--file", action="append", help="证据文件路径,可重复")
parser.add_argument("--provided", action="append", help="已具备的证据项,如 image/sitekey/pageurl")
parser.add_argument("--pretty", action="store_true")
return parser
def main() -> int:
args = build_parser().parse_args()
report = build_report(args)
print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
return 0
if __name__ == "__main__":
raise SystemExit(main())