Spaces:
Paused
Paused
| """FunCaptcha cloud solver service (YesCaptcha / CapSolver compatible). | |
| Environment variables: | |
| CAPTCHA_CLIENT_KEY β Cloud solver API key (required) | |
| CAPTCHA_CLOUD_URL β Cloud solver base URL (default: https://api.yescaptcha.com) | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import time | |
| from typing import Optional | |
| import requests | |
| DEFAULT_CLOUD_URL = "https://api.yescaptcha.com" | |
| class FunCaptchaService: | |
| """Cloud-based FunCaptcha (Arkose Labs) solver.""" | |
| def __init__( | |
| self, | |
| client_key: str = "", | |
| cloud_url: str = "", | |
| ): | |
| self.client_key = client_key or os.environ.get("CAPTCHA_CLIENT_KEY", "") | |
| self.cloud_url = ( | |
| cloud_url or os.environ.get("CAPTCHA_CLOUD_URL", "") or DEFAULT_CLOUD_URL | |
| ).rstrip("/") | |
| def solve( | |
| self, | |
| website_url: str, | |
| public_key: str, | |
| subdomain: Optional[str] = None, | |
| blob_data: Optional[str] = None, | |
| ) -> Optional[str]: | |
| """Submit FunCaptcha task and poll for token. | |
| Args: | |
| website_url: The page URL where FunCaptcha is loaded. | |
| public_key: Arkose Labs public key (pk= parameter from iframe src). | |
| subdomain: Optional custom subdomain (e.g. "client-api.arkoselabs.com"). | |
| blob_data: Optional blob data from the challenge. | |
| Returns: | |
| Solved token string, or None on failure. | |
| """ | |
| if not self.client_key: | |
| print("[Captcha] CAPTCHA_CLIENT_KEY not set") | |
| return None | |
| task_id = self._create_task(website_url, public_key, subdomain, blob_data) | |
| if not task_id: | |
| return None | |
| return self._poll_result(task_id) | |
| def _create_task( | |
| self, | |
| website_url: str, | |
| public_key: str, | |
| subdomain: Optional[str], | |
| blob_data: Optional[str], | |
| ) -> Optional[str]: | |
| task: dict = { | |
| "type": "FunCaptchaTaskProxyless", | |
| "websiteURL": website_url, | |
| "websitePublicKey": public_key, | |
| } | |
| if subdomain: | |
| task["funcaptchaApiJSSubdomain"] = subdomain | |
| if blob_data: | |
| task["data"] = blob_data | |
| try: | |
| r = requests.post( | |
| f"{self.cloud_url}/createTask", | |
| json={"clientKey": self.client_key, "task": task}, | |
| timeout=15, | |
| ) | |
| data = r.json() | |
| if data.get("errorId") != 0: | |
| print(f"[Captcha] Create error: {data.get('errorDescription')}") | |
| return None | |
| return data.get("taskId") | |
| except Exception as exc: | |
| print(f"[Captcha] Create failed: {exc}") | |
| return None | |
| def _poll_result(self, task_id: str, max_retries: int = 60) -> Optional[str]: | |
| time.sleep(5) | |
| for _ in range(max_retries): | |
| try: | |
| r = requests.post( | |
| f"{self.cloud_url}/getTaskResult", | |
| json={"clientKey": self.client_key, "taskId": task_id}, | |
| timeout=15, | |
| ) | |
| data = r.json() | |
| if data.get("errorId") != 0: | |
| print(f"[Captcha] Poll error: {data.get('errorDescription')}") | |
| return None | |
| if data.get("status") == "ready": | |
| return (data.get("solution") or {}).get("token") | |
| if data.get("status") != "processing": | |
| return None | |
| except Exception: | |
| pass | |
| time.sleep(3) | |
| print("[Captcha] Solver timeout") | |
| return None | |