Files
any-auto-register/core/base_captcha.py
2026-03-30 03:44:09 +08:00

128 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""验证码解决器基类"""
from abc import ABC, abstractmethod
import os
def _default_solver_url() -> str:
return os.getenv("LOCAL_SOLVER_URL") or f"http://127.0.0.1:{os.getenv('SOLVER_PORT', '8889')}"
class BaseCaptcha(ABC):
@abstractmethod
def solve_turnstile(self, page_url: str, site_key: str) -> str:
"""返回 Turnstile token"""
...
@abstractmethod
def solve_image(self, image_b64: str) -> str:
"""返回图片验证码文字"""
...
class YesCaptcha(BaseCaptcha):
def __init__(self, client_key: str):
self.client_key = client_key
self.api = "https://api.yescaptcha.com"
def solve_turnstile(self, page_url: str, site_key: str) -> str:
import requests, time, urllib3
urllib3.disable_warnings()
r = requests.post(f"{self.api}/createTask", json={
"clientKey": self.client_key,
"task": {"type": "TurnstileTaskProxyless",
"websiteURL": page_url, "websiteKey": site_key}
}, timeout=30, verify=False)
task_id = r.json().get("taskId")
if not task_id:
raise RuntimeError(f"YesCaptcha 创建任务失败: {r.text}")
for _ in range(60):
time.sleep(3)
d = requests.post(f"{self.api}/getTaskResult", json={
"clientKey": self.client_key, "taskId": task_id
}, timeout=30, verify=False).json()
if d.get("status") == "ready":
return d["solution"]["token"]
if d.get("errorId", 0) != 0:
raise RuntimeError(f"YesCaptcha 错误: {d}")
raise TimeoutError("YesCaptcha Turnstile 超时")
def solve_image(self, image_b64: str) -> str:
raise NotImplementedError
class ManualCaptcha(BaseCaptcha):
"""人工打码,阻塞等待用户输入"""
def solve_turnstile(self, page_url: str, site_key: str) -> str:
return input(f"请手动获取 Turnstile token ({page_url}): ").strip()
def solve_image(self, image_b64: str) -> str:
return input("请输入图片验证码: ").strip()
class LocalSolverCaptcha(BaseCaptcha):
"""调用本地 api_solver 服务解 TurnstileCamoufox/patchright"""
def __init__(self, solver_url: str | None = None):
self.solver_url = (solver_url or _default_solver_url()).rstrip("/")
def solve_turnstile(self, page_url: str, site_key: str) -> str:
import requests, time
# 提交任务
r = requests.get(
f"{self.solver_url}/turnstile",
params={"url": page_url, "sitekey": site_key},
timeout=15,
)
r.raise_for_status()
task_id = r.json().get("taskId")
if not task_id:
raise RuntimeError(f"LocalSolver 未返回 taskId: {r.text}")
# 轮询结果
for _ in range(60):
time.sleep(2)
res = requests.get(
f"{self.solver_url}/result",
params={"id": task_id},
timeout=10,
)
if res.status_code == 200:
data = res.json()
status = data.get("status")
if status == "ready":
token = data.get("solution", {}).get("token")
if token:
return token
elif status == "CAPTCHA_FAIL":
raise RuntimeError("LocalSolver Turnstile 失败")
raise TimeoutError("LocalSolver Turnstile 超时")
def solve_image(self, image_b64: str) -> str:
raise NotImplementedError
@staticmethod
def start_solver(headless: bool = True, browser_type: str = "camoufox",
port: int = 8889) -> None:
"""在后台线程启动本地 solver 服务"""
import subprocess, sys, os
solver_path = os.path.join(
os.path.dirname(__file__), "..", "services", "turnstile_solver", "start.py"
)
cmd = [
sys.executable, solver_path,
"--port", str(port),
"--browser_type", browser_type,
]
if not headless:
cmd.append("--no-headless")
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# 等待服务启动
import time, requests
for _ in range(20):
time.sleep(1)
try:
requests.get(f"http://localhost:{port}/", timeout=2)
return
except Exception:
pass
raise RuntimeError("LocalSolver 启动超时")