reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,436 @@
#!/usr/bin/env python3
"""评估第二阶段验证码验证尝试,并判断是否应切换到平台对照。"""
from __future__ import annotations
import argparse
import json
import sys
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()
MIN_FAILURES_FOR_SWITCH = 5
NO_ORDINARY_PLATFORM_TYPES = {"pow-challenge", "waf-challenge", "biometric-liveness"}
OK_STATUSES = {
"ok",
"pass",
"passed",
"success",
"successful",
"ready",
"clean",
"normal",
"valid",
"verified",
"no_issue",
"no-issue",
"none",
"无异常",
"正常",
"通过",
}
STATUS_ALIASES = {
"coordinate": "coordinates",
"coord": "coordinates",
"coords": "coordinates",
"motion": "track",
"trajectory": "track",
"tile": "tile_restore",
"restore": "tile_restore",
"env": "browser_env",
"environment": "browser_env",
"browser": "browser_env",
"freshness": "challenge_freshness",
"ttl": "challenge_freshness",
"challenge": "challenge_freshness",
}
AXIS_LABELS = {
"image": "图片/识别结果",
"coordinates": "坐标映射",
"track": "轨迹/交互行为",
"tile_restore": "切片乱序还原",
"browser_env": "补环境/浏览器环境",
"challenge_freshness": "challenge 新鲜度/TTL",
}
BLOCKING_ROUTE_BY_AXIS = {
"image": "fix-image-recognition",
"coordinates": "fix-coordinate-mapping",
"track": "fix-motion-track",
"tile_restore": "fix-tile-restore",
"browser_env": "fix-browser-env",
"challenge_freshness": "refresh-challenge",
}
PLATFORM_CANDIDATES = {
"text": ["云码/JFBYM", "超级鹰", "2Captcha ImageToText", "Anti-Captcha Image"],
"math": ["云码/JFBYM", "超级鹰", "2Captcha Normal Captcha"],
"slider": ["云码/JFBYM 滑块类型", "超级鹰滑块/坐标类型", "CapSolver", "2Captcha GeeTest/slider"],
"image-restore": ["云码/JFBYM 拼图/图片任务", "超级鹰滑块/坐标类型", "CapSolver 图片任务", "2Captcha 图片任务"],
"click-select": ["云码/JFBYM 坐标/点选类型", "超级鹰坐标类型", "2Captcha Coordinates", "CapSolver ComplexImageTask"],
"grid": ["2Captcha Grid/reCAPTCHA", "CapSolver", "CapMonster Cloud", "Anti-Captcha", "NopeCHA"],
"rotate": ["云码/JFBYM 旋转类型", "超级鹰旋转/坐标类型", "CapSolver 图片任务", "2Captcha 图片任务"],
"audio": ["2Captcha Audio", "CapMonster/Anti-Captcha 音频任务", "人工复核"],
"drag-drop": ["云码/JFBYM 坐标类型", "超级鹰坐标类型", "CapSolver ComplexImageTask", "人工接管"],
"trace-draw": ["云码/JFBYM 轨迹/坐标类型", "超级鹰坐标/轨迹类", "人工标注"],
"scratch": ["云码/JFBYM 定制类型", "超级鹰定制/人工", "人工接管"],
"area-select": ["云码/JFBYM 坐标/区域类型", "超级鹰坐标类型", "CapSolver ComplexImageTask"],
"difference-click": ["云码/JFBYM 坐标类型", "超级鹰坐标类型", "人工标注"],
"font-identify": ["云码/JFBYM 定制/坐标类型", "超级鹰人工/坐标类型", "人工标注"],
"semantic-reasoning": ["云码/JFBYM 定制/坐标类型", "超级鹰人工/坐标类型", "CapSolver ComplexImageTask"],
"game-challenge": ["2Captcha FunCaptcha/Arkose", "CapSolver FunCaptcha", "Anti-Captcha FunCaptcha", "NopeCHA", "人工接管"],
"risk-score": ["2Captcha reCAPTCHA v3/Enterprise", "CapSolver", "CapMonster Cloud", "Anti-Captcha"],
"one-click": ["2Captcha token 任务", "CapSolver", "CapMonster Cloud", "人工接管"],
"multi-step": ["按每轮子类型选择平台", "人工接管多轮流程"],
"qa-logic": ["云码/JFBYM 文字/定制类型", "超级鹰人工类型", "2Captcha Normal Captcha"],
"token-widget": ["2Captcha", "CapSolver", "CapMonster Cloud", "Anti-Captcha", "YesCaptcha/NoCaptchaAI"],
"unknown-custom": ["云码/JFBYM 定制", "超级鹰人工/定制", "2Captcha/CapSolver 通用图片或坐标任务"],
}
def load_json_source(value: str) -> Any:
stripped = value.lstrip()
if stripped.startswith("{") or stripped.startswith("["):
return json.loads(value)
path = Path(value)
try:
if path.exists() and path.is_file():
return json.loads(path.read_text(encoding="utf-8"))
except OSError:
pass
return json.loads(value)
def get_first(mapping: dict[str, Any], names: list[str], default: Any = None) -> Any:
for name in names:
if name in mapping and mapping[name] not in (None, ""):
return mapping[name]
return default
def normalize_attempts(data: Any) -> tuple[list[dict[str, Any]], dict[str, Any]]:
if isinstance(data, list):
return [item for item in data if isinstance(item, dict)], {}
if isinstance(data, dict):
attempts = data.get("attempts")
if isinstance(attempts, list):
defaults = {key: value for key, value in data.items() if key != "attempts"}
return [item for item in attempts if isinstance(item, dict)], defaults
return [data], {}
raise ValueError("attempts JSON 必须是数组,或包含 attempts 数组的对象")
def context_value(attempt: dict[str, Any], defaults: dict[str, Any], names: list[str], fallback: str) -> str:
value = get_first(attempt, names, get_first(defaults, names, fallback))
return str(value)
def attempt_context(attempt: dict[str, Any], defaults: dict[str, Any]) -> dict[str, str]:
return {
"authorization_scope": context_value(attempt, defaults, ["authorization_scope", "scope", "target"], "未声明授权目标"),
"captcha_type": context_value(attempt, defaults, ["captcha_type", "type"], "unknown-custom"),
"captcha_variant": context_value(attempt, defaults, ["captcha_variant", "variant"], ""),
"provider": context_value(attempt, defaults, ["provider"], "custom-or-unknown"),
"chosen_solution": context_value(attempt, defaults, ["chosen_solution", "solution", "route"], "未声明方案"),
}
def same_context(left: dict[str, str], right: dict[str, str]) -> bool:
return (
left["authorization_scope"] == right["authorization_scope"]
and left["captcha_type"] == right["captcha_type"]
and left["chosen_solution"] == right["chosen_solution"]
)
def is_success(attempt: dict[str, Any]) -> bool:
if isinstance(attempt.get("success"), bool):
return bool(attempt["success"])
status = str(get_first(attempt, ["status", "result", "outcome"], "")).strip().lower()
return status in {"success", "succeeded", "passed", "pass", "ok", "成功", "通过"}
def normalize_axis(name: str) -> str:
return STATUS_ALIASES.get(name.strip().lower(), name.strip().lower())
def normalize_status(value: Any) -> str:
if isinstance(value, bool):
return "ok" if value else "issue"
if value is None:
return "unknown"
status = str(value).strip().lower()
if not status:
return "unknown"
return "ok" if status in OK_STATUSES else status
def extract_diagnosis(attempt: dict[str, Any]) -> dict[str, str]:
raw = get_first(attempt, ["diagnosis_status", "diagnosis", "checks"], {})
diagnosis: dict[str, str] = {}
if isinstance(raw, dict):
for key, value in raw.items():
normalized = normalize_axis(str(key))
if isinstance(value, dict):
value = get_first(value, ["status", "result", "state"], "unknown")
diagnosis[normalized] = normalize_status(value)
for key, axis in [
("image_status", "image"),
("coordinate_status", "coordinates"),
("coordinates_status", "coordinates"),
("track_status", "track"),
("tile_restore_status", "tile_restore"),
("browser_env_status", "browser_env"),
("environment_status", "browser_env"),
("challenge_freshness_status", "challenge_freshness"),
("challenge_status", "challenge_freshness"),
]:
if key in attempt:
diagnosis[axis] = normalize_status(attempt[key])
return diagnosis
def applicable_axes(captcha_type: str, captcha_variant: str) -> list[str]:
if captcha_type in {"text", "math", "audio", "qa-logic"}:
return ["image", "challenge_freshness"]
if captcha_type == "slider":
return ["image", "coordinates", "track", "browser_env", "challenge_freshness"]
if captcha_type == "image-restore":
axes = ["image", "coordinates", "track", "browser_env", "challenge_freshness"]
if captcha_variant == "tile-scramble":
axes.insert(1, "tile_restore")
return axes
if captcha_type in {"click-select", "grid", "area-select", "difference-click", "font-identify", "semantic-reasoning"}:
return ["image", "coordinates", "browser_env", "challenge_freshness"]
if captcha_type in {"rotate", "drag-drop", "trace-draw", "scratch"}:
return ["image", "coordinates", "track", "browser_env", "challenge_freshness"]
if captcha_type in {"token-widget", "risk-score", "one-click", "game-challenge", "multi-step"}:
return ["browser_env", "challenge_freshness"]
if captcha_type in NO_ORDINARY_PLATFORM_TYPES:
return ["browser_env", "challenge_freshness"]
return ["image", "coordinates", "browser_env", "challenge_freshness"]
def infer_blocking_axis_from_text(attempts: list[dict[str, Any]]) -> str | None:
text = "\n".join(
str(get_first(attempt, ["failure_reason", "reason", "error", "notes"], ""))
for attempt in attempts
).lower()
keyword_axes = [
("tile_restore", ["乱序", "切片", "分块", "tile", "restore", "scramble", "还原"]),
("coordinates", ["坐标", "偏移", "映射", "dpr", "coordinate", "offset"]),
("track", ["轨迹", "速度", "拖动", "motion", "track", "trajectory"]),
("browser_env", ["补环境", "环境", "指纹", "cdp", "webdriver", "browser", "fingerprint"]),
("challenge_freshness", ["过期", "ttl", "expired", "stale", "刷新", "challenge id"]),
("image", ["图片", "识别", "ocr", "缺口", "角度", "目标", "image", "recognition"]),
]
for axis, keywords in keyword_axes:
if any(keyword in text for keyword in keywords):
return axis
return None
def find_blocking_issue(
recent_attempts: list[dict[str, Any]],
context: dict[str, str],
) -> tuple[str | None, dict[str, str]]:
latest_diagnosis = extract_diagnosis(recent_attempts[-1]) if recent_attempts else {}
axes = applicable_axes(context["captcha_type"], context["captcha_variant"])
normalized = {axis: latest_diagnosis.get(axis, "unknown") for axis in axes}
for axis in axes:
if normalized[axis] != "ok":
return axis, normalized
inferred = infer_blocking_axis_from_text(recent_attempts)
if inferred and inferred in axes:
normalized[inferred] = "suspected"
return inferred, normalized
return None, normalized
def candidates_for(context: dict[str, str]) -> list[str]:
captcha_type = context["captcha_type"]
if captcha_type == "image-restore" and context.get("captcha_variant") == "tile-scramble":
return ["云码/JFBYM 拼图/图片任务", "超级鹰坐标/拼图类型", "CapSolver 图片任务", "2Captcha 图片任务", "人工复核"]
return PLATFORM_CANDIDATES.get(captcha_type, PLATFORM_CANDIDATES["unknown-custom"])
def special_route_for(captcha_type: str) -> tuple[str, list[str], str] | None:
if captcha_type == "pow-challenge":
return (
"official-protocol-diagnostics",
[],
"PoW/工作量证明不默认推荐普通打码平台;优先检查官方协议、difficulty、nonce、TTL、防重放和服务端签名绑定。",
)
if captcha_type == "waf-challenge":
return (
"browser-env-and-vendor-diagnostics",
[],
"WAF challenge 不等同普通图片验证码;优先做浏览器环境、TLS/HTTP 指纹、IP/session 绑定和厂商日志诊断。",
)
if captcha_type == "biometric-liveness":
return (
"manual-review-or-vendor-support",
[],
"活体/人脸验证涉及隐私与合规,不默认推荐普通打码平台;优先走官方 SDK、人工复核、可访问性替代或厂商支持。",
)
return None
def build_platform_control_plan(context: dict[str, str], candidates: list[str]) -> dict[str, Any]:
return {
"role": "授权 QA 对照,不是默认替代所有本地方案",
"send_request": False,
"candidate_platforms": candidates,
"template_command": (
"python scripts/solver_request_template.py "
f"--platform <platform> --captcha-type {context['captcha_type']} "
f"--provider {context['provider']} --pretty"
),
"steps": [
"先生成请求模板和字段映射,只填非秘密字段。",
"让用户再次确认授权目标、平台、题型、代理/session 绑定要求和是否允许发送。",
"API key 只由用户运行时提供;不要写入文件、不要保存、不要回显。",
"平台结果只用于对照本地识别/交互链路与厂商服务端绑定问题。",
],
}
def evaluate(data: Any) -> dict[str, Any]:
attempts, defaults = normalize_attempts(data)
if not attempts:
return {
"switch_triggered": False,
"reason": "没有可评估的验证尝试记录。",
"blocking_issue": "attempts-missing",
"recommended_next_route": "collect-attempts",
"platform_candidates": [],
"attempt_summary": {"total_attempts": 0, "consecutive_same_context": 0, "failures": 0, "successes": 0},
"diagnosis_status": {},
"platform_control_plan": {},
"requires_user_confirmation": ["补充 attempts JSON 后再复盘"],
"send_request": False,
}
context = attempt_context(attempts[-1], defaults)
consecutive: list[dict[str, Any]] = []
for attempt in reversed(attempts):
if same_context(attempt_context(attempt, defaults), context):
consecutive.append(attempt)
else:
break
consecutive.reverse()
successes = sum(1 for attempt in consecutive if is_success(attempt))
failures = len(consecutive) - successes
special_route = special_route_for(context["captcha_type"])
platform_candidates = candidates_for(context) if not special_route else special_route[1]
blocking_axis, diagnosis_status = find_blocking_issue(consecutive, context)
summary = {
"total_attempts": len(attempts),
"consecutive_same_context": len(consecutive),
"failures": failures,
"successes": successes,
"context": context,
"failure_threshold": MIN_FAILURES_FOR_SWITCH,
}
base: dict[str, Any] = {
"attempt_summary": summary,
"diagnosis_status": diagnosis_status,
"platform_candidates": platform_candidates,
"platform_control_plan": build_platform_control_plan(context, platform_candidates) if platform_candidates else {},
"requires_user_confirmation": [
"实际发送打码平台请求前,必须再次确认授权范围、平台、题型和执行动作。",
"API key 只在运行时由用户提供,不保存、不回显。",
],
"send_request": False,
}
if special_route:
route, candidates, reason = special_route
return {
**base,
"switch_triggered": False,
"escalation_decision": "ordinary-platform-not-recommended",
"reason": reason,
"blocking_issue": None,
"recommended_next_route": route,
"platform_candidates": candidates,
"platform_control_plan": {},
}
if successes:
return {
**base,
"switch_triggered": False,
"escalation_decision": "keep-and-optimize",
"reason": "同一授权目标和同一方案下已有成功记录;当前方案可用,但需要继续优化稳定性和失败样本原因。",
"blocking_issue": None,
"recommended_next_route": "optimize-current-route",
}
if failures < MIN_FAILURES_FOR_SWITCH:
return {
**base,
"switch_triggered": False,
"escalation_decision": "wait-for-more-evidence",
"reason": f"连续失败 {failures} 次,未达到 {MIN_FAILURES_FOR_SWITCH} 次失败复盘门槛。",
"blocking_issue": None,
"recommended_next_route": "continue-current-route-with-diagnostics",
}
if blocking_axis:
return {
**base,
"switch_triggered": False,
"escalation_decision": "fix-blocking-issue-first",
"reason": f"已达到失败次数门槛,但仍存在明确阻塞项:{AXIS_LABELS.get(blocking_axis, blocking_axis)}。",
"blocking_issue": blocking_axis,
"recommended_next_route": BLOCKING_ROUTE_BY_AXIS.get(blocking_axis, "fix-blocking-issue"),
}
return {
**base,
"switch_triggered": True,
"escalation_decision": "recommend-platform-control",
"reason": (
f"同一授权目标、同一验证码类型、同一方案连续 {failures} 次验证失败且无成功记录,"
"图片/坐标/轨迹/还原/补环境/challenge 新鲜度未见明显异常;建议切换到打码平台做授权 QA 对照。"
),
"blocking_issue": None,
"recommended_next_route": "platform-control",
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="评估验证码验证尝试是否应切换方案")
parser.add_argument("--attempts", required=True, help="attempts JSON 文件路径,或直接传 JSON 字符串")
parser.add_argument("--pretty", action="store_true", help="以缩进格式输出 JSON")
parser.add_argument("--json", action="store_true", help="兼容参数;脚本始终输出 JSON")
return parser
def main() -> int:
args = build_parser().parse_args()
report = evaluate(load_json_source(args.attempts))
print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
return 0
if __name__ == "__main__":
raise SystemExit(main())