diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 071733b..368b88e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,9 +10,14 @@ permissions: contents: read jobs: + # Reuse the full lint + test workflow so a release never ships from a + # broken tree, even if the tag was pushed without a CI run on the commit. test: uses: ./.github/workflows/tests.yml + # Build sdist and wheel once and hand them to the publish job as an + # artifact, so what gets uploaded to PyPI is exactly what was built here. + # Also fails fast if the git tag disagrees with _version.py. build: needs: test runs-on: ubuntu-latest @@ -46,10 +51,13 @@ jobs: name: dist path: dist/ + # Upload the built artifacts to PyPI via trusted publishing, then create + # the matching GitHub Release with auto-generated notes and the same + # sdist/wheel attached. Only runs on an actual version tag -- + # workflow_dispatch runs stop at the build/test stage so you can dry-run + # the release without shipping it. publish: needs: build - # Only publish on an actual version tag -- workflow_dispatch runs stop at - # the build/test stage so you can dry-run the release without shipping it. if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest environment: @@ -59,6 +67,8 @@ jobs: # Required for PyPI's trusted publishing (OIDC) -- no API token/secret needed. # Configure this repo as a trusted publisher at https://pypi.org/manage/project/captcha-solver-api/settings/publishing/ id-token: write + # Required to create the GitHub Release. + contents: write steps: - name: Download build artifacts uses: actions/download-artifact@v4 @@ -68,3 +78,11 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: dist/* + # Requires Discussions enabled in the repo with this category present. + discussion_category_name: Announcements diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 241b4a9..75bc448 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,12 +1,43 @@ name: Tests on: + # Run on pushes to main only; feature branches are covered by the + # pull_request trigger, so this avoids a duplicate run per commit. push: + branches: [main] pull_request: workflow_call: jobs: + # Static checks: ruff lint + formatting and mypy type checking. Runs once + # on a single Python version since the result does not depend on the + # interpreter. + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install package with dev dependencies + run: python -m pip install -e ".[dev]" + + - name: Ruff lint + run: ruff check . + + - name: Ruff format check + run: ruff format --check . + + - name: mypy + run: mypy captcha_solver_api + + # Test suite across all supported Python versions, plus a build check + # to catch packaging errors early. Skipped if lint fails to save CI minutes. test: + needs: lint runs-on: ubuntu-latest strategy: matrix: diff --git a/README.md b/README.md index eef232f..e92254f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ [![Tests](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml) [![Typed](https://img.shields.io/badge/typing-typed-blue)](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/) +[![GitHub Release](https://img.shields.io/github/v/release/captcha-solver-api/python-sdk)](https://github.com/captcha-solver-api/python-sdk/releases) [![JavaScript SDK](https://img.shields.io/badge/also_available-JavaScript_SDK-f7df1e?logo=javascript&logoColor=black)](https://github.com/captcha-solver-api/javascript-sdk) Official Python SDK for the Captcha Solver API. Solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, Yandex SmartCaptcha, Tencent, and image/click captchas with a single method call -- sync or async. diff --git a/captcha_solver_api/__init__.py b/captcha_solver_api/__init__.py index d9c397d..d91df9e 100644 --- a/captcha_solver_api/__init__.py +++ b/captcha_solver_api/__init__.py @@ -13,26 +13,26 @@ result = client.solve(task) """ -from .client import CaptchaClient +from ._version import __version__ from .async_client import AsyncCaptchaClient +from .client import CaptchaClient from .exceptions import ( - CaptchaError, ApiError, - NetworkError, + CaptchaError, CaptchaTimeoutError, + NetworkError, TimeoutError, ValidationError, ) -from ._version import __version__ - __all__ = [ - "CaptchaClient", + "ApiError", "AsyncCaptchaClient", + "CaptchaClient", "CaptchaError", - "ApiError", - "NetworkError", "CaptchaTimeoutError", + "NetworkError", "TimeoutError", "ValidationError", + "__version__", ] diff --git a/captcha_solver_api/async_client.py b/captcha_solver_api/async_client.py index eae63e7..b55fc29 100644 --- a/captcha_solver_api/async_client.py +++ b/captcha_solver_api/async_client.py @@ -15,8 +15,8 @@ from ._version import __version__ from .exceptions import ( ApiError, - NetworkError, CaptchaTimeoutError, + NetworkError, ValidationError, ) @@ -87,7 +87,7 @@ async def aclose(self) -> None: (`async with AsyncCaptchaClient(...) as c:`) to have it closed automatically.""" await self._client.aclose() - async def __aenter__(self) -> "AsyncCaptchaClient": + async def __aenter__(self) -> AsyncCaptchaClient: return self async def __aexit__(self, *exc_info: Any) -> None: @@ -113,6 +113,8 @@ async def _request( except ValueError as exc: raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc + if not isinstance(data, dict): + raise NetworkError(f"Unexpected API response shape: {type(data).__name__}") return data def _ensure_success(self, data: Dict[str, Any]) -> None: @@ -154,7 +156,7 @@ async def create_task(self, task: Any, language_pool: Optional[str] = None) -> i data = await self._request("createTask", payload) self._ensure_success(data) - return data["taskId"] + return int(data["taskId"]) async def get_task_result(self, task_id: int) -> Dict[str, Any]: """Fetch the current status of a previously submitted task. @@ -203,7 +205,7 @@ async def get_balance(self) -> float: payload = {"clientKey": self.client_key} data = await self._request("getBalance", payload) self._ensure_success(data) - return data["balance"] + return float(data["balance"]) async def solve( self, @@ -248,6 +250,9 @@ async def solve( result = await self.get_task_result(task_id) if result.get("status") == "ready": - return result["solution"] + solution = result["solution"] + if not isinstance(solution, dict): + raise NetworkError("API returned a ready task without a solution object") + return solution raise CaptchaTimeoutError("Task solving timed out.") diff --git a/captcha_solver_api/client.py b/captcha_solver_api/client.py index 95e8262..d7b7573 100644 --- a/captcha_solver_api/client.py +++ b/captcha_solver_api/client.py @@ -12,8 +12,8 @@ from ._version import __version__ from .exceptions import ( ApiError, - NetworkError, CaptchaTimeoutError, + NetworkError, ValidationError, ) @@ -76,7 +76,7 @@ def close(self) -> None: to have it closed automatically.""" self.session.close() - def __enter__(self) -> "CaptchaClient": + def __enter__(self) -> CaptchaClient: return self def __exit__(self, *exc_info: Any) -> None: @@ -102,6 +102,8 @@ def _request( except ValueError as exc: raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc + if not isinstance(data, dict): + raise NetworkError(f"Unexpected API response shape: {type(data).__name__}") return data def _ensure_success(self, data: Dict[str, Any]) -> None: @@ -141,7 +143,7 @@ def create_task(self, task: Any, language_pool: Optional[str] = None) -> int: data = self._request("createTask", payload) self._ensure_success(data) - return data["taskId"] + return int(data["taskId"]) def get_task_result(self, task_id: int) -> Dict[str, Any]: """Fetches the current status of a task created with `create_task()`. @@ -187,7 +189,7 @@ def get_balance(self) -> float: payload = {"clientKey": self.client_key} data = self._request("getBalance", payload) self._ensure_success(data) - return data["balance"] + return float(data["balance"]) def solve( self, @@ -230,6 +232,9 @@ def solve( result = self.get_task_result(task_id) if result.get("status") == "ready": - return result["solution"] + solution = result["solution"] + if not isinstance(solution, dict): + raise NetworkError("API returned a ready task without a solution object") + return solution raise CaptchaTimeoutError("Task solving timed out.") diff --git a/captcha_solver_api/exceptions.py b/captcha_solver_api/exceptions.py index c3e7ef2..7198085 100644 --- a/captcha_solver_api/exceptions.py +++ b/captcha_solver_api/exceptions.py @@ -6,20 +6,14 @@ class CaptchaError(Exception): """Base exception for all SDK errors.""" - pass - class NetworkError(CaptchaError): """Raised when a network request fails.""" - pass - class CaptchaTimeoutError(CaptchaError): """Raised when the operation exceeds the configured timeout.""" - pass - # Deprecated alias kept for backward compatibility. It shadows the built-in # ``TimeoutError`` when imported by name, so prefer ``CaptchaTimeoutError``. @@ -37,5 +31,3 @@ def __init__(self, error_code: str, error_description: str) -> None: class ValidationError(CaptchaError): """Raised for client-side argument problems caught before any request is sent.""" - - pass \ No newline at end of file diff --git a/captcha_solver_api/tasks.py b/captcha_solver_api/tasks.py index 09a3f63..cec0f27 100644 --- a/captcha_solver_api/tasks.py +++ b/captcha_solver_api/tasks.py @@ -12,7 +12,7 @@ from __future__ import annotations -from typing import Any, Dict, List, Optional +from typing import Any, Dict, Optional class BaseTask: diff --git a/examples/async/balance.py b/examples/async/balance.py index 7c14832..01a670a 100644 --- a/examples/async/balance.py +++ b/examples/async/balance.py @@ -13,14 +13,14 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient @@ -30,7 +30,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -40,7 +40,7 @@ async def main(): # Returns a float with the available amount in your account currency. try: balance = await solver.get_balance() - print('Balance: ' + str(balance)) + print("Balance: " + str(balance)) except Exception as e: sys.exit(e) diff --git a/examples/async/coordinates.py b/examples/async/coordinates.py index c773cdb..1814002 100644 --- a/examples/async/coordinates.py +++ b/examples/async/coordinates.py @@ -16,20 +16,20 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) -assets_dir = os.path.join(repo_root, 'examples', 'assets') + load_dotenv(os.path.join(repo_root, ".env")) +assets_dir = os.path.join(repo_root, "examples", "assets") from captcha_solver_api import AsyncCaptchaClient from captcha_solver_api.tasks import CoordinatesTask -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -41,16 +41,18 @@ async def main(): try: # Read and encode the captcha image to base64. # The body must be a pure base64 string without the data:image/...;base64, prefix. - with open(os.path.join(assets_dir, 'fruit-click.png'), 'rb') as f: - body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "fruit-click.png"), "rb") as f: + body = b64encode(f.read()).decode("utf-8") - result = await solver.solve(CoordinatesTask( - body=body, # Base64-encoded captcha image (required) - comment='click on the green apple', # Text hint for the worker - )) + result = await solver.solve( + CoordinatesTask( + body=body, # Base64-encoded captcha image (required) + comment="click on the green apple", # Text hint for the worker + ) + ) # Solution contains {"coordinates": [{"x": 140, "y": 110}]} # Click on each coordinate in order. Coordinates are pixel positions. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -58,24 +60,26 @@ async def main(): # Solves a captcha (examples/assets/traffic-lights.png) with instruction image # and click count limits. try: - with open(os.path.join(assets_dir, 'traffic-lights.png'), 'rb') as f: - body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "traffic-lights.png"), "rb") as f: + body = b64encode(f.read()).decode("utf-8") # Read and encode an optional instruction image. # This image helps the worker understand what to click. - with open(os.path.join(assets_dir, 'traffic-lights-instructions.png'), 'rb') as f: - img_instructions = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "traffic-lights-instructions.png"), "rb") as f: + img_instructions = b64encode(f.read()).decode("utf-8") - result = await solver.solve(CoordinatesTask( - body=body, # Base64-encoded captcha image - comment='click on all traffic lights', # Text hint for the worker - imgInstructions=img_instructions, # Optional instruction image - minClicks=1, # Minimum number of clicks (default 1) - maxClicks=3, # Maximum number of clicks allowed - )) + result = await solver.solve( + CoordinatesTask( + body=body, # Base64-encoded captcha image + comment="click on all traffic lights", # Text hint for the worker + imgInstructions=img_instructions, # Optional instruction image + minClicks=1, # Minimum number of clicks (default 1) + maxClicks=3, # Maximum number of clicks allowed + ) + ) # Solution contains coordinates for all requested clicks, e.g. # {"coordinates": [{"x": 110, "y": 150}, {"x": 430, "y": 130}]} - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/geetest_v3.py b/examples/async/geetest_v3.py index 69a0d97..1443530 100644 --- a/examples/async/geetest_v3.py +++ b/examples/async/geetest_v3.py @@ -17,22 +17,23 @@ import sys import httpx + try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import GeeTestTaskProxyless, GeeTestTask +from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # GeeTest tasks may take longer. Increase timeout if needed. solver = AsyncCaptchaClient(api_key, timeout=300, polling_interval=10) @@ -48,43 +49,47 @@ async def main(): # In production, extract this from the page's initGeetest call or network requests. # "target-site.com" is a placeholder -- point this at your real target before running. async with httpx.AsyncClient() as client: - resp = await client.get('https://target-site.com/path/to/geetest/init', timeout=30) - challenge = resp.json()['challenge'] + resp = await client.get("https://target-site.com/path/to/geetest/init", timeout=30) + challenge = resp.json()["challenge"] # --- Proxyless example --- # Solves GeeTest v3 without a proxy. # v3 is the default version, so the version field can be omitted. try: - result = await solver.solve(GeeTestTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with GeeTest - gt='f2ae6cadcf7886856696c46d84d109d1', # Public key of the GeeTest widget - challenge=challenge, # Session-specific value, must be fresh - # Optional fields - # geetestApiServerSubdomain='api-na.geetest.com', # Custom API subdomain - # initParameters={...}, # Extra params from initGeetest call - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - )) + result = await solver.solve( + GeeTestTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with GeeTest + gt="f2ae6cadcf7886856696c46d84d109d1", # Public key of the GeeTest widget + challenge=challenge, # Session-specific value, must be fresh + # Optional fields + # geetestApiServerSubdomain='api-na.geetest.com', # Custom API subdomain + # initParameters={...}, # Extra params from initGeetest call + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + ) + ) # Solution contains {"challenge": "...", "validate": "...", "seccode": "..."} # Pass solution.validate and solution.seccode to the page's GeeTest callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves GeeTest v3 through your own proxy. try: - result = await solver.solve(GeeTestTask( - websiteURL='https://example.com/login', # Full URL of the page with GeeTest - gt='f2ae6cadcf7886856696c46d84d109d1', # Public key of the GeeTest widget - challenge=challenge, # Session-specific value, must be fresh - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - )) - print('result: ' + str(result)) + result = await solver.solve( + GeeTestTask( + websiteURL="https://example.com/login", # Full URL of the page with GeeTest + gt="f2ae6cadcf7886856696c46d84d109d1", # Public key of the GeeTest widget + challenge=challenge, # Session-specific value, must be fresh + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/geetest_v4.py b/examples/async/geetest_v4.py index a4e3822..0c5f002 100644 --- a/examples/async/geetest_v4.py +++ b/examples/async/geetest_v4.py @@ -16,19 +16,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import GeeTestTaskProxyless, GeeTestTask +from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # GeeTest v4 tasks may take longer. Increase timeout if needed. solver = AsyncCaptchaClient(api_key, timeout=300, polling_interval=10) @@ -39,38 +39,42 @@ async def main(): # Solves GeeTest v4 without a proxy. # v4 drops gt/challenge. The widget is identified by captcha_id inside initParameters. try: - result = await solver.solve(GeeTestTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the GeeTest widget - version=4, # Required: must be 4 for this version - initParameters={ # Required: must contain captcha_id - 'captcha_id': 'YOUR_CAPTCHA_ID', # Static site identifier, from that page - }, - # Optional fields - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - )) + result = await solver.solve( + GeeTestTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with the GeeTest widget + version=4, # Required: must be 4 for this version + initParameters={ # Required: must contain captcha_id + "captcha_id": "YOUR_CAPTCHA_ID", # Static site identifier, from that page + }, + # Optional fields + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + ) + ) # Solution contains {"captcha_id": "...", "lot_number": "...", "pass_token": "...", "gen_time": "...", "captcha_output": "..."} # Pass these values together into the page's GeeTest v4 callback as-is. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves GeeTest v4 through your own proxy. try: - result = await solver.solve(GeeTestTask( - websiteURL='https://example.com/login', # Full URL of the page with the GeeTest widget - version=4, # Required: must be 4 for this version - initParameters={ # Required: must contain captcha_id - 'captcha_id': 'YOUR_CAPTCHA_ID', # Static site identifier, from that page - }, - # --- Proxy parameters --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - )) - print('result: ' + str(result)) + result = await solver.solve( + GeeTestTask( + websiteURL="https://example.com/login", # Full URL of the page with the GeeTest widget + version=4, # Required: must be 4 for this version + initParameters={ # Required: must contain captcha_id + "captcha_id": "YOUR_CAPTCHA_ID", # Static site identifier, from that page + }, + # --- Proxy parameters --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/image_to_text.py b/examples/async/image_to_text.py index b8d9549..189a083 100644 --- a/examples/async/image_to_text.py +++ b/examples/async/image_to_text.py @@ -14,28 +14,28 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) -assets_dir = os.path.join(repo_root, 'examples', 'assets') + load_dotenv(os.path.join(repo_root, ".env")) +assets_dir = os.path.join(repo_root, "examples", "assets") from captcha_solver_api import AsyncCaptchaClient from captcha_solver_api.tasks import ImageToTextTask -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) async def main(): # Base64 body must not include the data:image/...;base64, prefix. - with open(os.path.join(assets_dir, 'captcha-digits.png'), 'rb') as f: - body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "captcha-digits.png"), "rb") as f: + body = b64encode(f.read()).decode("utf-8") task = ImageToTextTask(body=body, numeric=1, minLength=4, maxLength=6) @@ -45,7 +45,7 @@ async def main(): sys.exit(str(e)) else: # Solution contains {"text": "58204"} - print('result: ' + str(result)) + print("result: " + str(result)) asyncio.run(main()) diff --git a/examples/async/recaptcha_v2.py b/examples/async/recaptcha_v2.py index 9467046..ddbbd27 100644 --- a/examples/async/recaptcha_v2.py +++ b/examples/async/recaptcha_v2.py @@ -13,27 +13,27 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient from captcha_solver_api.tasks import RecaptchaV2TaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) async def main(): task = RecaptchaV2TaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the captcha - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value on that page + websiteURL="https://example.com/login", # Full URL of the page with the captcha + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value on that page ) try: @@ -41,7 +41,7 @@ async def main(): except Exception as e: sys.exit(str(e)) else: - print('result: ' + str(result)) + print("result: " + str(result)) asyncio.run(main()) diff --git a/examples/async/recaptcha_v2_enterprise.py b/examples/async/recaptcha_v2_enterprise.py index aa81d53..aabb7a8 100644 --- a/examples/async/recaptcha_v2_enterprise.py +++ b/examples/async/recaptcha_v2_enterprise.py @@ -14,19 +14,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import RecaptchaV2EnterpriseTaskProxyless, RecaptchaV2EnterpriseTask +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask, RecaptchaV2EnterpriseTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -38,19 +38,21 @@ async def main(): # If the site passes extra parameters to grecaptcha.enterprise.render(), # you must pass them as enterprisePayload or the token will be rejected. try: - result = await solver.solve(RecaptchaV2EnterpriseTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the Enterprise-protected page - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - isInvisible=False, # Set True for invisible reCAPTCHA - # Optional fields (pass only if the target site requires them) - # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() - # apiDomain='recaptcha.net', # Set if site loads captcha from recaptcha.net - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = await solver.solve( + RecaptchaV2EnterpriseTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the Enterprise-protected page + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + isInvisible=False, # Set True for invisible reCAPTCHA + # Optional fields (pass only if the target site requires them) + # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() + # apiDomain='recaptcha.net', # Set if site loads captcha from recaptcha.net + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains {"gRecaptchaResponse": "03AGdBq..."} # Pass this token to the g-recaptcha-response field or widget callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -58,23 +60,25 @@ async def main(): # Solves reCAPTCHA v2 Enterprise through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. try: - result = await solver.solve(RecaptchaV2EnterpriseTask( - websiteURL='https://example.com/login', # Full URL of the Enterprise-protected page - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # --- Optional fields --- - isInvisible=False, - # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() - userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - cookies='foo=bar; baz=1', # Session cookies if needed - )) + result = await solver.solve( + RecaptchaV2EnterpriseTask( + websiteURL="https://example.com/login", # Full URL of the Enterprise-protected page + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # --- Optional fields --- + isInvisible=False, + # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() + userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...", # Browser User-Agent + cookies="foo=bar; baz=1", # Session cookies if needed + ) + ) # Solution contains the same gRecaptchaResponse token. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/recaptcha_v3.py b/examples/async/recaptcha_v3.py index ba01fd8..8af759b 100644 --- a/examples/async/recaptcha_v3.py +++ b/examples/async/recaptcha_v3.py @@ -15,19 +15,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient from captcha_solver_api.tasks import RecaptchaV3TaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # reCAPTCHA v3 tasks may take longer to solve. Increase timeout if needed. solver = AsyncCaptchaClient(api_key, timeout=180) @@ -38,18 +38,20 @@ async def main(): # The higher the minScore you request, the harder and longer the task takes. # minScore values: 0.3 (fastest), 0.7 (balanced), 0.9 (highest, slowest). try: - result = await solver.solve(RecaptchaV3TaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the v3 widget - websiteKey='YOUR_WEBSITE_KEY', # Site key of the v3 widget on that page - minScore=0.3, # Minimum acceptable score (0.3, 0.7, or 0.9) - # Optional fields (pass if the site uses them, increases token acceptance) - pageAction='homepage', # Action set by that page in grecaptcha.execute() - # isEnterprise=True, # Set True for reCAPTCHA v3 Enterprise - # apiDomain='www.recaptcha.net', # Set if site loads from recaptcha.net - )) + result = await solver.solve( + RecaptchaV3TaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with the v3 widget + websiteKey="YOUR_WEBSITE_KEY", # Site key of the v3 widget on that page + minScore=0.3, # Minimum acceptable score (0.3, 0.7, or 0.9) + # Optional fields (pass if the site uses them, increases token acceptance) + pageAction="homepage", # Action set by that page in grecaptcha.execute() + # isEnterprise=True, # Set True for reCAPTCHA v3 Enterprise + # apiDomain='www.recaptcha.net', # Set if site loads from recaptcha.net + ) + ) # Solution contains {"gRecaptchaResponse": "03AGdBq..."} # Pass this token to the g-recaptcha-response field or grecaptcha callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/tencent.py b/examples/async/tencent.py index d32feca..28c9df3 100644 --- a/examples/async/tencent.py +++ b/examples/async/tencent.py @@ -14,19 +14,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import TencentTaskProxyless, TencentTask +from captcha_solver_api.tasks import TencentTask, TencentTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -37,15 +37,17 @@ async def main(): # The service's own proxies are used to solve the captcha. # appId is found in the page source code. captchaScript is optional if the site uses the default. try: - result = await solver.solve(TencentTaskProxyless( - websiteURL='https://example.com/register', # Full URL of the page using Tencent captcha - appId='YOUR_APP_ID', # appId from page source code (required) - # Optional fields: - captchaScript='https://captchacdn.tencentcloudcs.com/TCaptcha-global.js', # Custom script URL if non-default - )) + result = await solver.solve( + TencentTaskProxyless( + websiteURL="https://example.com/register", # Full URL of the page using Tencent captcha + appId="YOUR_APP_ID", # appId from page source code (required) + # Optional fields: + captchaScript="https://captchacdn.tencentcloudcs.com/TCaptcha-global.js", # Custom script URL if non-default + ) + ) # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} # Pass all four values together into the page's captcha callback as-is. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -53,19 +55,21 @@ async def main(): # Solves Tencent captcha through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. try: - result = await solver.solve(TencentTask( - websiteURL='https://example.com/register', # Full URL of the page with captcha - appId='YOUR_APP_ID', # appId from page source code (required) - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - captchaScript='https://captchacdn.tencentcloudcs.com/TCaptcha-global.js', - )) + result = await solver.solve( + TencentTask( + websiteURL="https://example.com/register", # Full URL of the page with captcha + appId="YOUR_APP_ID", # appId from page source code (required) + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + captchaScript="https://captchacdn.tencentcloudcs.com/TCaptcha-global.js", + ) + ) # Solution contains the same appid, ret, ticket, and randstr values. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/turnstile.py b/examples/async/turnstile.py index 5177b36..40f9154 100644 --- a/examples/async/turnstile.py +++ b/examples/async/turnstile.py @@ -14,19 +14,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import TurnstileTaskProxyless, TurnstileTask +from captcha_solver_api.tasks import TurnstileTask, TurnstileTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -37,40 +37,44 @@ async def main(): # The token is tied to the User-Agent. If you pass userAgent, use the same # User-Agent in your browser or bot when submitting the token. try: - result = await solver.solve(TurnstileTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with a Turnstile widget - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # Optional fields (pass only if the target site sets them) - # action='login', # Value of data-action attribute - # data='custom-cdata-value', # Value of data-cdata attribute - # pagedata='chl-page-data-value', # Value of chlPageData parameter - # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', # Must match the browser submitting the token - )) + result = await solver.solve( + TurnstileTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with a Turnstile widget + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # Optional fields (pass only if the target site sets them) + # action='login', # Value of data-action attribute + # data='custom-cdata-value', # Value of data-cdata attribute + # pagedata='chl-page-data-value', # Value of chlPageData parameter + # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', # Must match the browser submitting the token + ) + ) # Solution contains {"token": "0.zxcv..."} # Pass this token to the widget callback or cf-turnstile-response field. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves Cloudflare Turnstile through your own proxy. try: - result = await solver.solve(TurnstileTask( - websiteURL='https://example.com/login', # Full URL of the page with Turnstile - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # --- Optional fields --- - # action='login', - # data='custom-cdata-value', - # pagedata='chl-page-data-value', - # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', - )) - print('result: ' + str(result)) + result = await solver.solve( + TurnstileTask( + websiteURL="https://example.com/login", # Full URL of the page with Turnstile + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # --- Optional fields --- + # action='login', + # data='custom-cdata-value', + # pagedata='chl-page-data-value', + # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/async/yandex_smartcaptcha.py b/examples/async/yandex_smartcaptcha.py index c4f3398..698eec5 100644 --- a/examples/async/yandex_smartcaptcha.py +++ b/examples/async/yandex_smartcaptcha.py @@ -13,19 +13,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import YandexSmartCaptchaTaskProxyless, YandexSmartCaptchaTask +from captcha_solver_api.tasks import YandexSmartCaptchaTask, YandexSmartCaptchaTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = AsyncCaptchaClient(api_key) @@ -36,16 +36,18 @@ async def main(): # The service's own proxies are used to solve the captcha. # websiteKey is the sitekey value from the page code or captcha iframe. try: - result = await solver.solve(YandexSmartCaptchaTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page using SmartCaptcha - websiteKey='YOUR_WEBSITE_KEY', # sitekey from that page - # Optional fields: - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = await solver.solve( + YandexSmartCaptchaTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page using SmartCaptcha + websiteKey="YOUR_WEBSITE_KEY", # sitekey from that page + # Optional fields: + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} # Use solution.token in the smart-token field or pass to your site's backend. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -53,21 +55,23 @@ async def main(): # Solves Yandex SmartCaptcha through your own proxy. # Note: this is the only captcha type where an https proxy is accepted. try: - result = await solver.solve(YandexSmartCaptchaTask( - websiteURL='https://example.com/login', # Full URL of the page using SmartCaptcha - websiteKey='YOUR_WEBSITE_KEY', # sitekey from that page - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, https, socks4, or socks5 (https is accepted only for this type) - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # Optional fields: - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = await solver.solve( + YandexSmartCaptchaTask( + websiteURL="https://example.com/login", # Full URL of the page using SmartCaptcha + websiteKey="YOUR_WEBSITE_KEY", # sitekey from that page + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, https, socks4, or socks5 (https is accepted only for this type) + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # Optional fields: + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains the same token. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/balance.py b/examples/sync/balance.py index 36a2420..f3a6ae7 100644 --- a/examples/sync/balance.py +++ b/examples/sync/balance.py @@ -12,14 +12,14 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient @@ -29,7 +29,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. solver = CaptchaClient(api_key) @@ -38,6 +38,6 @@ # Returns a float with the available amount in your account currency. try: balance = solver.get_balance() - print('Balance: ' + str(balance)) + print("Balance: " + str(balance)) except Exception as e: - sys.exit(e) \ No newline at end of file + sys.exit(e) diff --git a/examples/sync/coordinates.py b/examples/sync/coordinates.py index ac3cde9..59c3421 100644 --- a/examples/sync/coordinates.py +++ b/examples/sync/coordinates.py @@ -15,15 +15,15 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) -assets_dir = os.path.join(repo_root, 'examples', 'assets') + load_dotenv(os.path.join(repo_root, ".env")) +assets_dir = os.path.join(repo_root, "examples", "assets") from captcha_solver_api import CaptchaClient from captcha_solver_api.tasks import CoordinatesTask @@ -34,7 +34,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. solver = CaptchaClient(api_key) @@ -45,16 +45,18 @@ try: # Read and encode the captcha image to base64. # The body must be a pure base64 string without the data:image/...;base64, prefix. - with open(os.path.join(assets_dir, 'fruit-click.png'), 'rb') as f: - body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "fruit-click.png"), "rb") as f: + body = b64encode(f.read()).decode("utf-8") - result = solver.solve(CoordinatesTask( - body=body, # Base64-encoded captcha image (required) - comment='click on the green apple', # Text hint for the worker - )) + result = solver.solve( + CoordinatesTask( + body=body, # Base64-encoded captcha image (required) + comment="click on the green apple", # Text hint for the worker + ) + ) # Solution contains {"coordinates": [{"x": 140, "y": 110}]} # Click on each coordinate in order. Coordinates are pixel positions. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -62,23 +64,25 @@ # Solves a captcha (examples/assets/traffic-lights.png) with instruction image # and click count limits. try: - with open(os.path.join(assets_dir, 'traffic-lights.png'), 'rb') as f: - body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "traffic-lights.png"), "rb") as f: + body = b64encode(f.read()).decode("utf-8") # Read and encode an optional instruction image. # This image helps the worker understand what to click. - with open(os.path.join(assets_dir, 'traffic-lights-instructions.png'), 'rb') as f: - img_instructions = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "traffic-lights-instructions.png"), "rb") as f: + img_instructions = b64encode(f.read()).decode("utf-8") - result = solver.solve(CoordinatesTask( - body=body, # Base64-encoded captcha image - comment='click on all traffic lights', # Text hint for the worker - imgInstructions=img_instructions, # Optional instruction image - minClicks=1, # Minimum number of clicks (default 1) - maxClicks=3, # Maximum number of clicks allowed - )) + result = solver.solve( + CoordinatesTask( + body=body, # Base64-encoded captcha image + comment="click on all traffic lights", # Text hint for the worker + imgInstructions=img_instructions, # Optional instruction image + minClicks=1, # Minimum number of clicks (default 1) + maxClicks=3, # Maximum number of clicks allowed + ) + ) # Solution contains coordinates for all requested clicks, e.g. # {"coordinates": [{"x": 110, "y": 150}, {"x": 430, "y": 130}]} - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/geetest_v3.py b/examples/sync/geetest_v3.py index 5b3f1a1..199479e 100644 --- a/examples/sync/geetest_v3.py +++ b/examples/sync/geetest_v3.py @@ -16,20 +16,21 @@ import sys import requests + try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import GeeTestTaskProxyless, GeeTestTask +from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless # in this example we store the API key inside environment variables that can be set like: # export CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS @@ -37,7 +38,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. # GeeTest tasks may take longer. Increase timeout if needed. @@ -52,41 +53,45 @@ # In production, extract this from the page's initGeetest call or network requests. # "target-site.com" is a placeholder -- point this at your real target before running. resp = requests.get("https://target-site.com/path/to/geetest/init", timeout=30) -challenge = resp.json()['challenge'] +challenge = resp.json()["challenge"] # --- Proxyless example --- # Solves GeeTest v3 without a proxy. # v3 is the default version, so the version field can be omitted. try: - result = solver.solve(GeeTestTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with GeeTest - gt='f2ae6cadcf7886856696c46d84d109d1', # Public key of the GeeTest widget - challenge=challenge, # Session-specific value, must be fresh - # Optional fields - # geetestApiServerSubdomain='api-na.geetest.com', # Custom API subdomain - # initParameters={...}, # Extra params from initGeetest call - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - )) + result = solver.solve( + GeeTestTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with GeeTest + gt="f2ae6cadcf7886856696c46d84d109d1", # Public key of the GeeTest widget + challenge=challenge, # Session-specific value, must be fresh + # Optional fields + # geetestApiServerSubdomain='api-na.geetest.com', # Custom API subdomain + # initParameters={...}, # Extra params from initGeetest call + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + ) + ) # Solution contains {"challenge": "...", "validate": "...", "seccode": "..."} # Pass solution.validate and solution.seccode to the page's GeeTest callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves GeeTest v3 through your own proxy. try: - result = solver.solve(GeeTestTask( - websiteURL='https://example.com/login', # Full URL of the page with GeeTest - gt='f2ae6cadcf7886856696c46d84d109d1', # Public key of the GeeTest widget - challenge=challenge, # Session-specific value, must be fresh - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - )) - print('result: ' + str(result)) + result = solver.solve( + GeeTestTask( + websiteURL="https://example.com/login", # Full URL of the page with GeeTest + gt="f2ae6cadcf7886856696c46d84d109d1", # Public key of the GeeTest widget + challenge=challenge, # Session-specific value, must be fresh + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/geetest_v4.py b/examples/sync/geetest_v4.py index faf4abf..dc066f0 100644 --- a/examples/sync/geetest_v4.py +++ b/examples/sync/geetest_v4.py @@ -15,17 +15,17 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import GeeTestTaskProxyless, GeeTestTask +from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless # in this example we store the API key inside environment variables that can be set like: # export CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS @@ -33,7 +33,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. # GeeTest v4 tasks may take longer. Increase timeout if needed. @@ -43,37 +43,41 @@ # Solves GeeTest v4 without a proxy. # v4 drops gt/challenge. The widget is identified by captcha_id inside initParameters. try: - result = solver.solve(GeeTestTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the GeeTest widget - version=4, # Required: must be 4 for this version - initParameters={ # Required: must contain captcha_id - 'captcha_id': 'YOUR_CAPTCHA_ID', # Static site identifier, from that page - }, - # Optional fields - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - )) + result = solver.solve( + GeeTestTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with the GeeTest widget + version=4, # Required: must be 4 for this version + initParameters={ # Required: must contain captcha_id + "captcha_id": "YOUR_CAPTCHA_ID", # Static site identifier, from that page + }, + # Optional fields + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + ) + ) # Solution contains {"captcha_id": "...", "lot_number": "...", "pass_token": "...", "gen_time": "...", "captcha_output": "..."} # Pass these values together into the page's GeeTest v4 callback as-is. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves GeeTest v4 through your own proxy. try: - result = solver.solve(GeeTestTask( - websiteURL='https://example.com/login', # Full URL of the page with the GeeTest widget - version=4, # Required: must be 4 for this version - initParameters={ # Required: must contain captcha_id - 'captcha_id': 'YOUR_CAPTCHA_ID', # Static site identifier, from that page - }, - # --- Proxy parameters --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - )) - print('result: ' + str(result)) + result = solver.solve( + GeeTestTask( + websiteURL="https://example.com/login", # Full URL of the page with the GeeTest widget + version=4, # Required: must be 4 for this version + initParameters={ # Required: must contain captcha_id + "captcha_id": "YOUR_CAPTCHA_ID", # Static site identifier, from that page + }, + # --- Proxy parameters --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + ) + ) + print("result: " + str(result)) except Exception as e: - sys.exit(e) \ No newline at end of file + sys.exit(e) diff --git a/examples/sync/image_to_text.py b/examples/sync/image_to_text.py index 0f2f227..892d693 100644 --- a/examples/sync/image_to_text.py +++ b/examples/sync/image_to_text.py @@ -14,15 +14,15 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) -assets_dir = os.path.join(repo_root, 'examples', 'assets') + load_dotenv(os.path.join(repo_root, ".env")) +assets_dir = os.path.join(repo_root, "examples", "assets") from captcha_solver_api import CaptchaClient from captcha_solver_api.tasks import ImageToTextTask @@ -33,7 +33,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. # Image to Text tasks are usually fast. Default timeout is fine. @@ -45,18 +45,20 @@ try: # Read and encode the captcha image to base64. # The body must be a pure base64 string without the data:image/...;base64, prefix. - with open(os.path.join(assets_dir, 'captcha-digits.png'), 'rb') as f: - digits_body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "captcha-digits.png"), "rb") as f: + digits_body = b64encode(f.read()).decode("utf-8") - result = solver.solve(ImageToTextTask( - body=digits_body, # Base64-encoded image (required) - numeric=1, # 1 = digits only - minLength=4, # Minimum expected answer length - maxLength=6, # Maximum expected answer length - )) + result = solver.solve( + ImageToTextTask( + body=digits_body, # Base64-encoded image (required) + numeric=1, # 1 = digits only + minLength=4, # Minimum expected answer length + maxLength=6, # Maximum expected answer length + ) + ) # Solution contains {"text": "58204"} # Submit solution.text to the target form field. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -64,26 +66,28 @@ # Solves a math captcha (examples/assets/captcha-math.png) with comment and # instruction image (examples/assets/captcha-math-instructions.png). try: - with open(os.path.join(assets_dir, 'captcha-math.png'), 'rb') as f: - math_body = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "captcha-math.png"), "rb") as f: + math_body = b64encode(f.read()).decode("utf-8") # Read the hint image as base64. - with open(os.path.join(assets_dir, 'captcha-math-instructions.png'), 'rb') as f: - img_instructions = b64encode(f.read()).decode('utf-8') + with open(os.path.join(assets_dir, "captcha-math-instructions.png"), "rb") as f: + img_instructions = b64encode(f.read()).decode("utf-8") - result = solver.solve(ImageToTextTask( - body=math_body, # Base64-encoded captcha image - # Optional fields (pass only if needed by the captcha type) - phrase=False, # True if answer has multiple words - case=True, # True if answer is case-sensitive - numeric=0, # 0 = not specified, 1 = digits, 2 = letters, 3 = any with digits, 4 = any with letters - math=True, # True if image is a math expression to solve - minLength=1, # Minimum answer length - maxLength=10, # Maximum answer length - comment='Enter the result of the equation', # Text hint for the worker - imgInstructions=img_instructions, # Optional instruction image for the worker - )) - print('result: ' + str(result)) + result = solver.solve( + ImageToTextTask( + body=math_body, # Base64-encoded captcha image + # Optional fields (pass only if needed by the captcha type) + phrase=False, # True if answer has multiple words + case=True, # True if answer is case-sensitive + numeric=0, # 0 = not specified, 1 = digits, 2 = letters, 3 = any with digits, 4 = any with letters + math=True, # True if image is a math expression to solve + minLength=1, # Minimum answer length + maxLength=10, # Maximum answer length + comment="Enter the result of the equation", # Text hint for the worker + imgInstructions=img_instructions, # Optional instruction image for the worker + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -99,8 +103,8 @@ minLength=4, maxLength=6, ), - language_pool='en', # Picks English-speaking worker pool + language_pool="en", # Picks English-speaking worker pool ) - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/recaptcha_v2.py b/examples/sync/recaptcha_v2.py index 9ce73c0..9e76fe5 100644 --- a/examples/sync/recaptcha_v2.py +++ b/examples/sync/recaptcha_v2.py @@ -12,17 +12,17 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2TaskProxyless, RecaptchaV2Task +from captcha_solver_api.tasks import RecaptchaV2Task, RecaptchaV2TaskProxyless # in this example we store the API key inside environment variables that can be set like: # export CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS @@ -30,7 +30,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. # Optional: timeout (max seconds to wait for solution, default 120) @@ -43,14 +43,16 @@ try: # Create a task and wait for the solution. # solve() handles task creation, polling, and returns the solution dict. - result = solver.solve(RecaptchaV2TaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the captcha - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value on that page - isInvisible=False, # Set True for invisible reCAPTCHA - )) + result = solver.solve( + RecaptchaV2TaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with the captcha + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value on that page + isInvisible=False, # Set True for invisible reCAPTCHA + ) + ) # Solution contains {"gRecaptchaResponse": "03AGdBq..."} # Pass this token to the g-recaptcha-response field or widget callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -58,17 +60,19 @@ # Solves reCAPTCHA v2 through your own proxy. # Required when the target site is geo-restricted or you need session consistency. try: - result = solver.solve(RecaptchaV2Task( - websiteURL='https://example.com/login', # Full URL of the page with captcha - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - )) + result = solver.solve( + RecaptchaV2Task( + websiteURL="https://example.com/login", # Full URL of the page with captcha + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + ) + ) # Solution contains the same gRecaptchaResponse token. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/recaptcha_v2_enterprise.py b/examples/sync/recaptcha_v2_enterprise.py index ac0beb1..9ac578a 100644 --- a/examples/sync/recaptcha_v2_enterprise.py +++ b/examples/sync/recaptcha_v2_enterprise.py @@ -13,17 +13,17 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2EnterpriseTaskProxyless, RecaptchaV2EnterpriseTask +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask, RecaptchaV2EnterpriseTaskProxyless # in this example we store the API key inside environment variables that can be set like: # export CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS @@ -31,7 +31,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. solver = CaptchaClient(api_key) @@ -42,19 +42,21 @@ # If the site passes extra parameters to grecaptcha.enterprise.render(), # you must pass them as enterprisePayload or the token will be rejected. try: - result = solver.solve(RecaptchaV2EnterpriseTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the Enterprise-protected page - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - isInvisible=False, # Set True for invisible reCAPTCHA - # Optional fields (pass only if the target site requires them) - # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() - # apiDomain='recaptcha.net', # Set if site loads captcha from recaptcha.net - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = solver.solve( + RecaptchaV2EnterpriseTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the Enterprise-protected page + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + isInvisible=False, # Set True for invisible reCAPTCHA + # Optional fields (pass only if the target site requires them) + # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() + # apiDomain='recaptcha.net', # Set if site loads captcha from recaptcha.net + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains {"gRecaptchaResponse": "03AGdBq..."} # Pass this token to the g-recaptcha-response field or widget callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -62,22 +64,24 @@ # Solves reCAPTCHA v2 Enterprise through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. try: - result = solver.solve(RecaptchaV2EnterpriseTask( - websiteURL='https://example.com/login', # Full URL of the Enterprise-protected page - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # --- Optional fields --- - isInvisible=False, - # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() - userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - cookies='foo=bar; baz=1', # Session cookies if needed - )) + result = solver.solve( + RecaptchaV2EnterpriseTask( + websiteURL="https://example.com/login", # Full URL of the Enterprise-protected page + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # --- Optional fields --- + isInvisible=False, + # enterprisePayload={'s': 'value-from-page'}, # Extra params from grecaptcha.enterprise.render() + userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...", # Browser User-Agent + cookies="foo=bar; baz=1", # Session cookies if needed + ) + ) # Solution contains the same gRecaptchaResponse token. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/recaptcha_v3.py b/examples/sync/recaptcha_v3.py index 8a58ef4..cd2164b 100644 --- a/examples/sync/recaptcha_v3.py +++ b/examples/sync/recaptcha_v3.py @@ -14,14 +14,14 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient from captcha_solver_api.tasks import RecaptchaV3TaskProxyless @@ -32,7 +32,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. # reCAPTCHA v3 tasks may take longer to solve. Increase timeout if needed. @@ -42,17 +42,19 @@ # The higher the minScore you request, the harder and longer the task takes. # minScore values: 0.3 (fastest), 0.7 (balanced), 0.9 (highest, slowest). try: - result = solver.solve(RecaptchaV3TaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with the v3 widget - websiteKey='YOUR_WEBSITE_KEY', # Site key of the v3 widget on that page - minScore=0.3, # Minimum acceptable score (0.3, 0.7, or 0.9) - # Optional fields (pass if the site uses them, increases token acceptance) - pageAction='homepage', # Action set by that page in grecaptcha.execute() - # isEnterprise=True, # Set True for reCAPTCHA v3 Enterprise - # apiDomain='www.recaptcha.net', # Set if site loads from recaptcha.net - )) + result = solver.solve( + RecaptchaV3TaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with the v3 widget + websiteKey="YOUR_WEBSITE_KEY", # Site key of the v3 widget on that page + minScore=0.3, # Minimum acceptable score (0.3, 0.7, or 0.9) + # Optional fields (pass if the site uses them, increases token acceptance) + pageAction="homepage", # Action set by that page in grecaptcha.execute() + # isEnterprise=True, # Set True for reCAPTCHA v3 Enterprise + # apiDomain='www.recaptcha.net', # Set if site loads from recaptcha.net + ) + ) # Solution contains {"gRecaptchaResponse": "03AGdBq..."} # Pass this token to the g-recaptcha-response field or grecaptcha callback. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/tencent.py b/examples/sync/tencent.py index 2d83cc1..86fe48d 100644 --- a/examples/sync/tencent.py +++ b/examples/sync/tencent.py @@ -13,19 +13,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TencentTaskProxyless, TencentTask +from captcha_solver_api.tasks import TencentTask, TencentTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = CaptchaClient(api_key) @@ -34,15 +34,17 @@ # The service's own proxies are used to solve the captcha. # appId is found in the page source code. captchaScript is optional if the site uses the default. try: - result = solver.solve(TencentTaskProxyless( - websiteURL='https://example.com/register', # Full URL of the page using Tencent captcha - appId='YOUR_APP_ID', # appId from page source code (required) - # Optional fields: - captchaScript='https://captchacdn.tencentcloudcs.com/TCaptcha-global.js', # Custom script URL if non-default - )) + result = solver.solve( + TencentTaskProxyless( + websiteURL="https://example.com/register", # Full URL of the page using Tencent captcha + appId="YOUR_APP_ID", # appId from page source code (required) + # Optional fields: + captchaScript="https://captchacdn.tencentcloudcs.com/TCaptcha-global.js", # Custom script URL if non-default + ) + ) # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} # Pass all four values together into the page's captcha callback as-is. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -50,18 +52,20 @@ # Solves Tencent captcha through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. try: - result = solver.solve(TencentTask( - websiteURL='https://example.com/register', # Full URL of the page with captcha - appId='YOUR_APP_ID', # appId from page source code (required) - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - captchaScript='https://captchacdn.tencentcloudcs.com/TCaptcha-global.js', - )) + result = solver.solve( + TencentTask( + websiteURL="https://example.com/register", # Full URL of the page with captcha + appId="YOUR_APP_ID", # appId from page source code (required) + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + captchaScript="https://captchacdn.tencentcloudcs.com/TCaptcha-global.js", + ) + ) # Solution contains the same appid, ret, ticket, and randstr values. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/turnstile.py b/examples/sync/turnstile.py index 3354b4c..bbc3743 100644 --- a/examples/sync/turnstile.py +++ b/examples/sync/turnstile.py @@ -13,17 +13,17 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TurnstileTaskProxyless, TurnstileTask +from captcha_solver_api.tasks import TurnstileTask, TurnstileTaskProxyless # in this example we store the API key inside environment variables that can be set like: # export CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Linux or macOS @@ -31,7 +31,7 @@ # you can just set the API key directly to its value like: # api_key="1abc234de56fab7c89012d34e56fa7b8" -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") # Create a solver instance with your API key. solver = CaptchaClient(api_key) @@ -41,39 +41,43 @@ # The token is tied to the User-Agent. If you pass userAgent, use the same # User-Agent in your browser or bot when submitting the token. try: - result = solver.solve(TurnstileTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page with a Turnstile widget - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # Optional fields (pass only if the target site sets them) - # action='login', # Value of data-action attribute - # data='custom-cdata-value', # Value of data-cdata attribute - # pagedata='chl-page-data-value', # Value of chlPageData parameter - # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', # Must match the browser submitting the token - )) + result = solver.solve( + TurnstileTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page with a Turnstile widget + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # Optional fields (pass only if the target site sets them) + # action='login', # Value of data-action attribute + # data='custom-cdata-value', # Value of data-cdata attribute + # pagedata='chl-page-data-value', # Value of chlPageData parameter + # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', # Must match the browser submitting the token + ) + ) # Solution contains {"token": "0.zxcv..."} # Pass this token to the widget callback or cf-turnstile-response field. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) # --- With proxy example --- # Solves Cloudflare Turnstile through your own proxy. try: - result = solver.solve(TurnstileTask( - websiteURL='https://example.com/login', # Full URL of the page with Turnstile - websiteKey='YOUR_WEBSITE_KEY', # data-sitekey attribute value - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, socks4, or socks5 - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # --- Optional fields --- - # action='login', - # data='custom-cdata-value', - # pagedata='chl-page-data-value', - # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', - )) - print('result: ' + str(result)) + result = solver.solve( + TurnstileTask( + websiteURL="https://example.com/login", # Full URL of the page with Turnstile + websiteKey="YOUR_WEBSITE_KEY", # data-sitekey attribute value + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, socks4, or socks5 + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # --- Optional fields --- + # action='login', + # data='custom-cdata-value', + # pagedata='chl-page-data-value', + # userAgent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...', + ) + ) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/examples/sync/yandex_smartcaptcha.py b/examples/sync/yandex_smartcaptcha.py index 39dbd01..11b328f 100644 --- a/examples/sync/yandex_smartcaptcha.py +++ b/examples/sync/yandex_smartcaptcha.py @@ -12,19 +12,19 @@ try: from dotenv import load_dotenv except ModuleNotFoundError as exc: - if exc.name != 'dotenv': + if exc.name != "dotenv": raise load_dotenv = None repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) sys.path.append(repo_root) if load_dotenv is not None: - load_dotenv(os.path.join(repo_root, '.env')) + load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import YandexSmartCaptchaTaskProxyless, YandexSmartCaptchaTask +from captcha_solver_api.tasks import YandexSmartCaptchaTask, YandexSmartCaptchaTaskProxyless -api_key = os.getenv('CAPTCHA_API_KEY', 'YOUR_API_KEY') +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = CaptchaClient(api_key) @@ -33,16 +33,18 @@ # The service's own proxies are used to solve the captcha. # websiteKey is the sitekey value from the page code or captcha iframe. try: - result = solver.solve(YandexSmartCaptchaTaskProxyless( - websiteURL='https://example.com/login', # Full URL of the page using SmartCaptcha - websiteKey='YOUR_WEBSITE_KEY', # sitekey from that page - # Optional fields: - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = solver.solve( + YandexSmartCaptchaTaskProxyless( + websiteURL="https://example.com/login", # Full URL of the page using SmartCaptcha + websiteKey="YOUR_WEBSITE_KEY", # sitekey from that page + # Optional fields: + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} # Use solution.token in the smart-token field or pass to your site's backend. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) @@ -50,20 +52,22 @@ # Solves Yandex SmartCaptcha through your own proxy. # Note: this is the only captcha type where an https proxy is accepted. try: - result = solver.solve(YandexSmartCaptchaTask( - websiteURL='https://example.com/login', # Full URL of the page using SmartCaptcha - websiteKey='YOUR_WEBSITE_KEY', # sitekey from that page - # --- Proxy parameters (replace with your own -- these are placeholders) --- - proxyType='http', # http, https, socks4, or socks5 (https is accepted only for this type) - proxyAddress='1.2.3.4', # Proxy IP address - proxyPort=8080, # Proxy port - proxyLogin='user', # Login for proxy authorization (optional) - proxyPassword='password', # Password for proxy authorization (optional) - # Optional fields: - # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent - # cookies='session=abc123; token=xyz789', # Session cookies if needed - )) + result = solver.solve( + YandexSmartCaptchaTask( + websiteURL="https://example.com/login", # Full URL of the page using SmartCaptcha + websiteKey="YOUR_WEBSITE_KEY", # sitekey from that page + # --- Proxy parameters (replace with your own -- these are placeholders) --- + proxyType="http", # http, https, socks4, or socks5 (https is accepted only for this type) + proxyAddress="1.2.3.4", # Proxy IP address + proxyPort=8080, # Proxy port + proxyLogin="user", # Login for proxy authorization (optional) + proxyPassword="password", # Password for proxy authorization (optional) + # Optional fields: + # userAgent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', # Browser User-Agent + # cookies='session=abc123; token=xyz789', # Session cookies if needed + ) + ) # Solution contains the same token. - print('result: ' + str(result)) + print("result: " + str(result)) except Exception as e: sys.exit(e) diff --git a/pyproject.toml b/pyproject.toml index 09eb81b..cc3bfd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,9 @@ dev = [ "pytest-asyncio>=0.21", "pytest-mock>=3", "python-dotenv>=1.0", + "ruff>=0.6", + "mypy>=1.10", + "types-requests>=2.28", ] [project.urls] @@ -63,3 +66,27 @@ captcha_solver_api = ["py.typed"] [tool.pytest.ini_options] asyncio_mode = "auto" + +[tool.mypy] +# mypy 2.x cannot target 3.9; 3.10 is the lowest it accepts. Runtime +# compatibility with 3.9 is still covered by the CI test matrix. +python_version = "3.10" +strict = true +files = ["captcha_solver_api"] + +[tool.ruff] +target-version = "py39" +line-length = 100 +extend-exclude = [".pytest_cache", "*.md"] + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "UP", "B", "N", "PIE", "RUF"] +ignore = [ + "UP006", "UP035", "UP045", # keep typing.Optional/Dict/List for 3.9 readability + "N803", "N815", # task fields mirror API camelCase on purpose + "E501", # long lines in docstrings/tables +] + +[tool.ruff.lint.per-file-ignores] +"examples/**" = ["E402"] # imports after sys.path/dotenv setup +"tests/**" = ["RUF012"] diff --git a/tests/async/test_async_client.py b/tests/async/test_async_client.py index 1cd3a27..355926a 100644 --- a/tests/async/test_async_client.py +++ b/tests/async/test_async_client.py @@ -9,7 +9,7 @@ import httpx import pytest -from captcha_solver_api import AsyncCaptchaClient, ApiError, CaptchaTimeoutError, ValidationError +from captcha_solver_api import ApiError, AsyncCaptchaClient, CaptchaTimeoutError, ValidationError from captcha_solver_api.tasks import RecaptchaV2TaskProxyless diff --git a/tests/async/test_async_coordinates.py b/tests/async/test_async_coordinates.py index cbe21ed..3b416ed 100644 --- a/tests/async/test_async_coordinates.py +++ b/tests/async/test_async_coordinates.py @@ -10,7 +10,6 @@ class TestAsyncCoordinates: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = CoordinatesTask(body="base64string", comment="click on the green apple") @@ -18,7 +17,11 @@ async def test_solve(self): with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 108}, - {"errorId": 0, "status": "ready", "solution": {"coordinates": [{"x": 358, "y": 268}]}}, + { + "errorId": 0, + "status": "ready", + "solution": {"coordinates": [{"x": 358, "y": 268}]}, + }, ] result = await client.solve(task) diff --git a/tests/async/test_async_geetest.py b/tests/async/test_async_geetest.py index 50cc901..f8e23a5 100644 --- a/tests/async/test_async_geetest.py +++ b/tests/async/test_async_geetest.py @@ -10,17 +10,24 @@ class TestAsyncGeeTest: - async def test_solve_v3(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) - task = GeeTestTaskProxyless(websiteURL="https://example.com", gt="test_gt", challenge="test_challenge") + task = GeeTestTaskProxyless( + websiteURL="https://example.com", gt="test_gt", challenge="test_challenge" + ) with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 105}, - {"errorId": 0, "status": "ready", "solution": { - "challenge": "c", "validate": "v", "seccode": "s", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "challenge": "c", + "validate": "v", + "seccode": "s", + }, + }, ] result = await client.solve(task) @@ -29,16 +36,25 @@ async def test_solve_v3(self): async def test_solve_v4(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = GeeTestTaskProxyless( - websiteURL="https://example.com", version=4, initParameters={"captcha_id": "test_id"}, + websiteURL="https://example.com", + version=4, + initParameters={"captcha_id": "test_id"}, ) with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 106}, - {"errorId": 0, "status": "ready", "solution": { - "captcha_id": "test_id", "lot_number": "1", "pass_token": "p", - "gen_time": "t", "captcha_output": "o", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "captcha_id": "test_id", + "lot_number": "1", + "pass_token": "p", + "gen_time": "t", + "captcha_output": "o", + }, + }, ] result = await client.solve(task) diff --git a/tests/async/test_async_image_to_text.py b/tests/async/test_async_image_to_text.py index 8b794c9..ce3517c 100644 --- a/tests/async/test_async_image_to_text.py +++ b/tests/async/test_async_image_to_text.py @@ -10,7 +10,6 @@ class TestAsyncImageToText: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = ImageToTextTask(body="base64string", numeric=1, minLength=4, maxLength=6) diff --git a/tests/async/test_async_recaptcha_v2.py b/tests/async/test_async_recaptcha_v2.py index c37c109..175b482 100644 --- a/tests/async/test_async_recaptcha_v2.py +++ b/tests/async/test_async_recaptcha_v2.py @@ -11,7 +11,6 @@ class TestAsyncRecaptchaV2: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = RecaptchaV2TaskProxyless(websiteURL="https://example.com", websiteKey="test_key") diff --git a/tests/async/test_async_recaptcha_v2_enterprise.py b/tests/async/test_async_recaptcha_v2_enterprise.py index 228a255..362a07f 100644 --- a/tests/async/test_async_recaptcha_v2_enterprise.py +++ b/tests/async/test_async_recaptcha_v2_enterprise.py @@ -10,15 +10,20 @@ class TestAsyncRecaptchaV2Enterprise: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) - task = RecaptchaV2EnterpriseTaskProxyless(websiteURL="https://example.com", websiteKey="test_key") + task = RecaptchaV2EnterpriseTaskProxyless( + websiteURL="https://example.com", websiteKey="test_key" + ) with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 101}, - {"errorId": 0, "status": "ready", "solution": {"gRecaptchaResponse": "enterprise_token"}}, + { + "errorId": 0, + "status": "ready", + "solution": {"gRecaptchaResponse": "enterprise_token"}, + }, ] result = await client.solve(task) diff --git a/tests/async/test_async_recaptcha_v3.py b/tests/async/test_async_recaptcha_v3.py index ade862a..38e76b0 100644 --- a/tests/async/test_async_recaptcha_v3.py +++ b/tests/async/test_async_recaptcha_v3.py @@ -10,10 +10,11 @@ class TestAsyncRecaptchaV3: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) - task = RecaptchaV3TaskProxyless(websiteURL="https://example.com", websiteKey="test_key", minScore=0.3) + task = RecaptchaV3TaskProxyless( + websiteURL="https://example.com", websiteKey="test_key", minScore=0.3 + ) with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ diff --git a/tests/async/test_async_tencent.py b/tests/async/test_async_tencent.py index 5a1efaf..3d06b90 100644 --- a/tests/async/test_async_tencent.py +++ b/tests/async/test_async_tencent.py @@ -10,7 +10,6 @@ class TestAsyncTencent: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = TencentTaskProxyless(websiteURL="https://example.com", appId="190014885") @@ -18,9 +17,16 @@ async def test_solve(self): with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 109}, - {"errorId": 0, "status": "ready", "solution": { - "appid": "190014885", "ret": 0, "ticket": "t", "randstr": "r", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "appid": "190014885", + "ret": 0, + "ticket": "t", + "randstr": "r", + }, + }, ] result = await client.solve(task) diff --git a/tests/async/test_async_turnstile.py b/tests/async/test_async_turnstile.py index 1eae698..29944f4 100644 --- a/tests/async/test_async_turnstile.py +++ b/tests/async/test_async_turnstile.py @@ -10,7 +10,6 @@ class TestAsyncTurnstile: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) task = TurnstileTaskProxyless(websiteURL="https://example.com", websiteKey="test_key") diff --git a/tests/async/test_async_yandex_smartcaptcha.py b/tests/async/test_async_yandex_smartcaptcha.py index 42c2a0d..bb842b3 100644 --- a/tests/async/test_async_yandex_smartcaptcha.py +++ b/tests/async/test_async_yandex_smartcaptcha.py @@ -10,10 +10,11 @@ class TestAsyncYandexSmartCaptcha: - async def test_solve(self): client = AsyncCaptchaClient("test_key", polling_interval=0.1) - task = YandexSmartCaptchaTaskProxyless(websiteURL="https://example.com", websiteKey="Y5Lh0ti...") + task = YandexSmartCaptchaTaskProxyless( + websiteURL="https://example.com", websiteKey="Y5Lh0ti..." + ) with patch.object(client, "_request", new_callable=AsyncMock) as mock_request: mock_request.side_effect = [ diff --git a/tests/sync/test_client.py b/tests/sync/test_client.py index efc6b3c..f8069b0 100644 --- a/tests/sync/test_client.py +++ b/tests/sync/test_client.py @@ -9,7 +9,13 @@ import pytest -from captcha_solver_api import CaptchaClient, ApiError, CaptchaTimeoutError, NetworkError, ValidationError +from captcha_solver_api import ( + ApiError, + CaptchaClient, + CaptchaTimeoutError, + NetworkError, + ValidationError, +) from captcha_solver_api.tasks import RecaptchaV2TaskProxyless diff --git a/tests/sync/test_coordinates.py b/tests/sync/test_coordinates.py index cac7609..d9509b2 100644 --- a/tests/sync/test_coordinates.py +++ b/tests/sync/test_coordinates.py @@ -9,7 +9,6 @@ class TestCoordinates: - def test_to_dict(self): task = CoordinatesTask( body="base64string", @@ -39,7 +38,11 @@ def test_solve(self): with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 108}, - {"errorId": 0, "status": "ready", "solution": {"coordinates": [{"x": 358, "y": 268}]}}, + { + "errorId": 0, + "status": "ready", + "solution": {"coordinates": [{"x": 358, "y": 268}]}, + }, ] result = client.solve(task) diff --git a/tests/sync/test_geetest.py b/tests/sync/test_geetest.py index cd1bab8..3e12023 100644 --- a/tests/sync/test_geetest.py +++ b/tests/sync/test_geetest.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import GeeTestTaskProxyless, GeeTestTask +from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless class TestGeeTest: - def test_v3_to_dict(self): task = GeeTestTaskProxyless( websiteURL="https://example.com", @@ -60,14 +59,22 @@ def test_task_with_proxy_to_dict(self): def test_solve_v3(self): client = CaptchaClient("test_key", polling_interval=0.1) - task = GeeTestTaskProxyless(websiteURL="https://example.com", gt="test_gt", challenge="test_challenge") + task = GeeTestTaskProxyless( + websiteURL="https://example.com", gt="test_gt", challenge="test_challenge" + ) with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 105}, - {"errorId": 0, "status": "ready", "solution": { - "challenge": "c", "validate": "v", "seccode": "s", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "challenge": "c", + "validate": "v", + "seccode": "s", + }, + }, ] result = client.solve(task) @@ -76,16 +83,25 @@ def test_solve_v3(self): def test_solve_v4(self): client = CaptchaClient("test_key", polling_interval=0.1) task = GeeTestTaskProxyless( - websiteURL="https://example.com", version=4, initParameters={"captcha_id": "test_id"}, + websiteURL="https://example.com", + version=4, + initParameters={"captcha_id": "test_id"}, ) with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 106}, - {"errorId": 0, "status": "ready", "solution": { - "captcha_id": "test_id", "lot_number": "1", "pass_token": "p", - "gen_time": "t", "captcha_output": "o", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "captcha_id": "test_id", + "lot_number": "1", + "pass_token": "p", + "gen_time": "t", + "captcha_output": "o", + }, + }, ] result = client.solve(task) diff --git a/tests/sync/test_image_to_text.py b/tests/sync/test_image_to_text.py index df6aa08..bde712d 100644 --- a/tests/sync/test_image_to_text.py +++ b/tests/sync/test_image_to_text.py @@ -9,7 +9,6 @@ class TestImageToText: - def test_to_dict(self): task = ImageToTextTask( body="base64string", diff --git a/tests/sync/test_recaptcha_v2.py b/tests/sync/test_recaptcha_v2.py index fd4e322..98ff3bb 100644 --- a/tests/sync/test_recaptcha_v2.py +++ b/tests/sync/test_recaptcha_v2.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2TaskProxyless, RecaptchaV2Task +from captcha_solver_api.tasks import RecaptchaV2Task, RecaptchaV2TaskProxyless class TestRecaptchaV2: - def test_proxyless_to_dict(self): task = RecaptchaV2TaskProxyless( websiteURL="https://example.com", diff --git a/tests/sync/test_recaptcha_v2_enterprise.py b/tests/sync/test_recaptcha_v2_enterprise.py index 125f60b..9b9af33 100644 --- a/tests/sync/test_recaptcha_v2_enterprise.py +++ b/tests/sync/test_recaptcha_v2_enterprise.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2EnterpriseTaskProxyless, RecaptchaV2EnterpriseTask +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask, RecaptchaV2EnterpriseTaskProxyless class TestRecaptchaV2Enterprise: - def test_proxyless_to_dict(self): task = RecaptchaV2EnterpriseTaskProxyless( websiteURL="https://example.com", @@ -54,12 +53,18 @@ def test_task_with_proxy_to_dict(self): def test_solve(self): client = CaptchaClient("test_key", polling_interval=0.1) - task = RecaptchaV2EnterpriseTaskProxyless(websiteURL="https://example.com", websiteKey="test_key") + task = RecaptchaV2EnterpriseTaskProxyless( + websiteURL="https://example.com", websiteKey="test_key" + ) with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 101}, - {"errorId": 0, "status": "ready", "solution": {"gRecaptchaResponse": "enterprise_token"}}, + { + "errorId": 0, + "status": "ready", + "solution": {"gRecaptchaResponse": "enterprise_token"}, + }, ] result = client.solve(task) diff --git a/tests/sync/test_recaptcha_v3.py b/tests/sync/test_recaptcha_v3.py index 9fc6e20..272db59 100644 --- a/tests/sync/test_recaptcha_v3.py +++ b/tests/sync/test_recaptcha_v3.py @@ -9,7 +9,6 @@ class TestRecaptchaV3: - def test_to_dict(self): task = RecaptchaV3TaskProxyless( websiteURL="https://example.com", @@ -24,7 +23,9 @@ def test_to_dict(self): def test_solve(self): client = CaptchaClient("test_key", polling_interval=0.1) - task = RecaptchaV3TaskProxyless(websiteURL="https://example.com", websiteKey="test_key", minScore=0.3) + task = RecaptchaV3TaskProxyless( + websiteURL="https://example.com", websiteKey="test_key", minScore=0.3 + ) with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ diff --git a/tests/sync/test_tencent.py b/tests/sync/test_tencent.py index 238fabc..5567dde 100644 --- a/tests/sync/test_tencent.py +++ b/tests/sync/test_tencent.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TencentTaskProxyless, TencentTask +from captcha_solver_api.tasks import TencentTask, TencentTaskProxyless class TestTencent: - def test_proxyless_to_dict(self): task = TencentTaskProxyless( websiteURL="https://example.com", @@ -56,9 +55,16 @@ def test_solve(self): with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ {"errorId": 0, "taskId": 109}, - {"errorId": 0, "status": "ready", "solution": { - "appid": "190014885", "ret": 0, "ticket": "t", "randstr": "r", - }}, + { + "errorId": 0, + "status": "ready", + "solution": { + "appid": "190014885", + "ret": 0, + "ticket": "t", + "randstr": "r", + }, + }, ] result = client.solve(task) diff --git a/tests/sync/test_turnstile.py b/tests/sync/test_turnstile.py index 725b537..035b53a 100644 --- a/tests/sync/test_turnstile.py +++ b/tests/sync/test_turnstile.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TurnstileTaskProxyless, TurnstileTask +from captcha_solver_api.tasks import TurnstileTask, TurnstileTaskProxyless class TestTurnstile: - def test_proxyless_to_dict(self): task = TurnstileTaskProxyless( websiteURL="https://example.com", diff --git a/tests/sync/test_yandex_smartcaptcha.py b/tests/sync/test_yandex_smartcaptcha.py index 2a6f7b3..68c1250 100644 --- a/tests/sync/test_yandex_smartcaptcha.py +++ b/tests/sync/test_yandex_smartcaptcha.py @@ -5,11 +5,10 @@ from unittest.mock import patch from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import YandexSmartCaptchaTaskProxyless, YandexSmartCaptchaTask +from captcha_solver_api.tasks import YandexSmartCaptchaTask, YandexSmartCaptchaTaskProxyless class TestYandexSmartCaptcha: - def test_proxyless_to_dict(self): task = YandexSmartCaptchaTaskProxyless( websiteURL="https://example.com", @@ -51,7 +50,9 @@ def test_task_with_proxy(self): def test_solve(self): client = CaptchaClient("test_key", polling_interval=0.1) - task = YandexSmartCaptchaTaskProxyless(websiteURL="https://example.com", websiteKey="Y5Lh0ti...") + task = YandexSmartCaptchaTaskProxyless( + websiteURL="https://example.com", websiteKey="Y5Lh0ti..." + ) with patch.object(client, "_request") as mock_request: mock_request.side_effect = [ diff --git a/tests/test_polling.py b/tests/test_polling.py index 85566a4..fdf1b37 100644 --- a/tests/test_polling.py +++ b/tests/test_polling.py @@ -5,9 +5,9 @@ import pytest -import captcha_solver_api.client as sync_module import captcha_solver_api.async_client as async_module -from captcha_solver_api import CaptchaClient, AsyncCaptchaClient, CaptchaTimeoutError +import captcha_solver_api.client as sync_module +from captcha_solver_api import AsyncCaptchaClient, CaptchaClient, CaptchaTimeoutError from captcha_solver_api.tasks import RecaptchaV2TaskProxyless @@ -30,60 +30,62 @@ def sleep(self, seconds): ] -@pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES) +@pytest.mark.parametrize("interval,timeout,expected_polls,times_out", CASES) def test_sync_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out): clock = Clock() polls = [] - monkeypatch.setattr(sync_module, 'time', clock) + monkeypatch.setattr(sync_module, "time", clock) def request(endpoint, payload): - if endpoint == 'createTask': + if endpoint == "createTask": assert clock.now == 0 - return {'errorId': 0, 'taskId': 100} - assert payload['taskId'] == 100 + return {"errorId": 0, "taskId": 100} + assert payload["taskId"] == 100 polls.append(clock.now) if len(polls) == 2: - return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}} - return {'errorId': 0, 'status': 'processing'} + return {"errorId": 0, "status": "ready", "solution": {"token": "done"}} + return {"errorId": 0, "status": "processing"} - options = {} if interval is None else {'polling_interval': interval} - with CaptchaClient('test-key', **options) as client: - monkeypatch.setattr(client, '_request', request) - task = RecaptchaV2TaskProxyless('https://example.com', 'site-key') + options = {} if interval is None else {"polling_interval": interval} + with CaptchaClient("test-key", **options) as client: + monkeypatch.setattr(client, "_request", request) + task = RecaptchaV2TaskProxyless("https://example.com", "site-key") if times_out: with pytest.raises(CaptchaTimeoutError): client.solve(task, timeout=timeout) assert clock.now == timeout else: - assert client.solve(task, timeout=timeout) == {'token': 'done'} + assert client.solve(task, timeout=timeout) == {"token": "done"} assert polls == expected_polls -@pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES) +@pytest.mark.parametrize("interval,timeout,expected_polls,times_out", CASES) async def test_async_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out): clock = Clock() polls = [] - monkeypatch.setattr(async_module, 'time', clock) - monkeypatch.setattr(async_module, 'asyncio', SimpleNamespace(sleep=AsyncMock(side_effect=clock.sleep))) + monkeypatch.setattr(async_module, "time", clock) + monkeypatch.setattr( + async_module, "asyncio", SimpleNamespace(sleep=AsyncMock(side_effect=clock.sleep)) + ) async def request(endpoint, payload): - if endpoint == 'createTask': + if endpoint == "createTask": assert clock.now == 0 - return {'errorId': 0, 'taskId': 100} - assert payload['taskId'] == 100 + return {"errorId": 0, "taskId": 100} + assert payload["taskId"] == 100 polls.append(clock.now) if len(polls) == 2: - return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}} - return {'errorId': 0, 'status': 'processing'} + return {"errorId": 0, "status": "ready", "solution": {"token": "done"}} + return {"errorId": 0, "status": "processing"} - options = {} if interval is None else {'polling_interval': interval} - async with AsyncCaptchaClient('test-key', **options) as client: - monkeypatch.setattr(client, '_request', request) - task = RecaptchaV2TaskProxyless('https://example.com', 'site-key') + options = {} if interval is None else {"polling_interval": interval} + async with AsyncCaptchaClient("test-key", **options) as client: + monkeypatch.setattr(client, "_request", request) + task = RecaptchaV2TaskProxyless("https://example.com", "site-key") if times_out: with pytest.raises(CaptchaTimeoutError): await client.solve(task, timeout=timeout) assert clock.now == timeout else: - assert await client.solve(task, timeout=timeout) == {'token': 'done'} + assert await client.solve(task, timeout=timeout) == {"token": "done"} assert polls == expected_polls