fix: слоты, безопасность, единый формат ответа и валидация

- слоты браузеров: asyncio.Semaphore, размер из BROWSER_SLOTS, при нехватке — 503 + Retry-After;
  устранена утечка счётчика при отказе (#12, #49)
- session_id принимается только как UUID, путь профиля проверяется (#5)
- секреты клиента в логах маскируются, полный дамп — только на DEBUG (#6)
- ответ: status всегда "ok"/"err", поле code (no_slots / request_failed / challenge_failed /
  action_failed), response_model (#7)
- непройденный антибот → challenge_failed, действия не выполняются (#8)
- таймауты провайдеров в мс вынесены в константы; detect переживает навигацию посреди
  проверки; пауза между неудачными solve вместо холостого цикла (#9)
- обязательные поля действий и формат прокси проверяются схемой → 422 до запуска браузера (#13)
- прокси необязателен (#15)
- user_agent сохраняется в профиле сессии и реально передаётся в браузер (#16)
- duration_ms у действий и у запроса (#18), опция screenshot (#19)
- общий конфиг логирования (LOG_LEVEL), print → логгер, CloudflareProvider,
  удалён cached_persistent, GET /health (#22)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-13 14:52:12 +04:00
1 parent f58306cbb4
commit 437ea27be7
14 files changed
+360 -201

No files matched your search

+7 -6
View File
@@ -35,15 +35,16 @@ def classify(resp: httpx.Response | Exception) -> str:
if isinstance(resp, Exception):
return f"exc:{type(resp).__name__}"
data = resp.json()
if "No slots" in str(data.get("error", "")):
code = data.get("code")
if code == "no_slots":
return "no_slots"
actions = data.get("actions") or []
if not actions:
return f"error:{str(data.get('error'))[:60]}"
last = actions[-1]
if not last.get("ok") or last.get("result"):
if code == "challenge_failed":
return "cf_blocked"
if code == "action_failed":
html = data.get("html") or ""
return "cf_blocked" if any(m in html for m in CF_MARKERS) else "action_failed"
if data.get("status") != "ok":
return f"error:{str(data.get('error'))[:60]}"
return "ok"