| """FlareSolverr-backed managed clearance provider.""" |
|
|
| import asyncio |
| import json |
| from urllib import request as urllib_request |
| from urllib.error import HTTPError, URLError |
| from urllib.parse import urlparse |
|
|
| from app.platform.logging.logger import logger |
| from app.platform.config.snapshot import get_config |
| from ..models import ClearanceBundle, ClearanceMode |
|
|
|
|
| def _extract_all_cookies(cookies: list[dict]) -> str: |
| return "; ".join(f"{c.get('name')}={c.get('value')}" for c in cookies) |
|
|
|
|
| class FlareSolverrClearanceProvider: |
| """Refresh CF clearance bundles via a FlareSolverr instance.""" |
|
|
| async def refresh_bundle( |
| self, |
| *, |
| affinity_key: str, |
| proxy_url: str, |
| target_url: str = "https://grok.com", |
| ) -> ClearanceBundle | None: |
| cfg = get_config() |
| mode = ClearanceMode.parse(cfg.get_str("proxy.clearance.mode", "none")) |
| if mode != ClearanceMode.FLARESOLVERR: |
| return None |
| fs_url = cfg.get_str("proxy.clearance.flaresolverr_url", "") |
| timeout_sec = cfg.get_int("proxy.clearance.timeout_sec", 60) |
| if not fs_url: |
| return None |
|
|
| result = await self._solve( |
| fs_url = fs_url, |
| proxy_url = proxy_url, |
| timeout_sec = timeout_sec, |
| target_url = target_url, |
| ) |
| if not result: |
| logger.warning( |
| "flaresolverr clearance refresh failed: affinity={} proxy={} target={}", |
| affinity_key, proxy_url or "<direct>", target_url, |
| ) |
| return None |
| host = result.get("clearance_host", "grok.com") |
|
|
| return ClearanceBundle( |
| bundle_id = f"flaresolverr:{affinity_key}@{host}", |
| cf_cookies = result.get("cookies", ""), |
| user_agent = result.get("user_agent", ""), |
| affinity_key = affinity_key, |
| clearance_host = host, |
| ) |
|
|
| async def _solve( |
| self, |
| *, |
| fs_url: str, |
| proxy_url: str, |
| timeout_sec: int, |
| target_url: str, |
| ) -> dict[str, str] | None: |
| target = target_url.strip() or "https://grok.com" |
| payload: dict = { |
| "cmd": "request.get", |
| "url": target, |
| "maxTimeout": timeout_sec * 1000, |
| } |
| if proxy_url: |
| payload["proxy"] = {"url": proxy_url} |
|
|
| body = json.dumps(payload).encode() |
| request = urllib_request.Request( |
| f"{fs_url.rstrip('/')}/v1", |
| data = body, |
| method = "POST", |
| headers = {"Content-Type": "application/json"}, |
| ) |
|
|
| try: |
| def _post() -> dict: |
| with urllib_request.urlopen(request, timeout=timeout_sec + 30) as resp: |
| return json.loads(resp.read().decode()) |
|
|
| result = await asyncio.to_thread(_post) |
| if result.get("status") != "ok": |
| logger.warning( |
| "flaresolverr returned non-ok status: status={} message={}", |
| result.get("status"), result.get("message", ""), |
| ) |
| return None |
|
|
| solution = result.get("solution", {}) |
| cookies = solution.get("cookies", []) |
| if not cookies: |
| logger.warning("flaresolverr returned no cookies") |
| return None |
|
|
| ua = solution.get("userAgent", "") or "" |
| host = (urlparse(target).hostname or "").lower() |
| filtered = [ |
| cookie for cookie in cookies |
| if not host or not cookie.get("domain") or host.endswith(str(cookie.get("domain", "")).lstrip(".").lower()) |
| ] |
| chosen = filtered or cookies |
| return { |
| "cookies": _extract_all_cookies(chosen), |
| "user_agent": ua, |
| "clearance_host": host or "grok.com", |
| } |
|
|
| except HTTPError as exc: |
| body_text = exc.read().decode("utf-8", "replace")[:300] |
| logger.warning("flaresolverr http request failed: status={} body={}", exc.code, body_text) |
| except URLError as exc: |
| logger.warning("flaresolverr connection failed: reason={}", exc.reason) |
| except Exception as exc: |
| logger.warning("flaresolverr request failed: error={}", exc) |
|
|
| return None |
|
|
|
|
| __all__ = ["FlareSolverrClearanceProvider"] |
|
|