From 180ce7b0da96be487a25dac2c06b4613830aa3c6 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:34:08 +0300 Subject: [PATCH 01/11] docs: link package metadata to SDK readme --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb344f3..9fa1f33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ dev = [ [project.urls] Homepage = "https://captcha-solver.com" -Documentation = "https://captcha-solver.com/en/docs/captcha-types" +Documentation = "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/captcha-solver-api/python-sdk#readme" Repository = "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/captcha-solver-api/python-sdk" [tool.setuptools.packages.find] From 8937693d2e8ee8a84811d71661c53c23e9480445 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:35:31 +0300 Subject: [PATCH 02/11] fix: validate required task parameters --- captcha_solver_api/async_client.py | 4 ++++ captcha_solver_api/client.py | 4 ++++ captcha_solver_api/tasks.py | 18 +++++++++++++++ tests/async/test_async_validation.py | 10 ++++++++ tests/sync/test_validation.py | 34 ++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 tests/async/test_async_validation.py create mode 100644 tests/sync/test_validation.py diff --git a/captcha_solver_api/async_client.py b/captcha_solver_api/async_client.py index faede47..0638f0e 100644 --- a/captcha_solver_api/async_client.py +++ b/captcha_solver_api/async_client.py @@ -19,6 +19,7 @@ NetworkError, ValidationError, ) +from .tasks import BaseTask class AsyncCaptchaClient: @@ -172,6 +173,9 @@ async def create_task(self, task: Any, language_pool: Optional[str] = None) -> i Example: task_id = await client.create_task(task, language_pool="en") """ + if not isinstance(task, BaseTask): + raise ValidationError("task must be an instance of BaseTask") + payload: Dict[str, Any] = { "clientKey": self.client_key, "task": task.to_dict(), diff --git a/captcha_solver_api/client.py b/captcha_solver_api/client.py index 6b85021..5eb45a6 100644 --- a/captcha_solver_api/client.py +++ b/captcha_solver_api/client.py @@ -16,6 +16,7 @@ NetworkError, ValidationError, ) +from .tasks import BaseTask class CaptchaClient: @@ -160,6 +161,9 @@ def create_task(self, task: Any, language_pool: Optional[str] = None) -> int: Example: task_id = client.create_task(task, language_pool="en") """ + if not isinstance(task, BaseTask): + raise ValidationError("task must be an instance of BaseTask") + payload: Dict[str, Any] = { "clientKey": self.client_key, "task": task.to_dict(), diff --git a/captcha_solver_api/tasks.py b/captcha_solver_api/tasks.py index 5df677e..871844e 100644 --- a/captcha_solver_api/tasks.py +++ b/captcha_solver_api/tasks.py @@ -12,8 +12,11 @@ from __future__ import annotations +import inspect from typing import Any, Dict, Optional +from .exceptions import ValidationError + class BaseTask: """Base class for all CAPTCHA tasks.""" @@ -23,12 +26,27 @@ class BaseTask: def to_dict(self) -> Dict[str, Any]: """Serializes the task to the dict shape expected by the `task` field of `createTask`, dropping any fields left as `None`.""" + self._validate_required_fields() result = {"type": self.type} for key, value in self.__dict__.items(): if value is not None: result[key] = value return result + def _validate_required_fields(self) -> None: + """Reject missing values for constructor parameters without defaults.""" + parameters = inspect.signature(type(self).__init__).parameters + missing = [] + for name, parameter in parameters.items(): + if name == "self" or parameter.default is not inspect.Parameter.empty: + continue + value = getattr(self, name, None) + if value is None or (isinstance(value, str) and not value.strip()): + missing.append(name) + if missing: + fields = ", ".join(missing) + raise ValidationError(f"Missing required task parameter(s): {fields}") + class ProxyMixin: """Mixin that adds proxy fields to a task. diff --git a/tests/async/test_async_validation.py b/tests/async/test_async_validation.py new file mode 100644 index 0000000..f07660a --- /dev/null +++ b/tests/async/test_async_validation.py @@ -0,0 +1,10 @@ +import pytest + +from captcha_solver_api import AsyncCaptchaClient, ValidationError + + +@pytest.mark.asyncio +async def test_create_task_rejects_non_task_value(): + async with AsyncCaptchaClient("api-key") as client: + with pytest.raises(ValidationError, match="task must be an instance of BaseTask"): + await client.create_task(None) diff --git a/tests/sync/test_validation.py b/tests/sync/test_validation.py new file mode 100644 index 0000000..2407116 --- /dev/null +++ b/tests/sync/test_validation.py @@ -0,0 +1,34 @@ +import pytest + +from captcha_solver_api import CaptchaClient, ValidationError +from captcha_solver_api.tasks import RecaptchaV2Task, RecaptchaV2TaskProxyless + + +def test_task_rejects_empty_required_fields_before_request(): + task = RecaptchaV2TaskProxyless(websiteURL=" ", websiteKey="") + + with pytest.raises( + ValidationError, + match=r"Missing required task parameter\(s\): websiteURL, websiteKey", + ): + task.to_dict() + + +def test_proxy_task_rejects_empty_required_proxy_fields(): + task = RecaptchaV2Task( + websiteURL="https://example.com", + websiteKey="site-key", + proxyType="", + proxyAddress="", + proxyPort=8080, + ) + + with pytest.raises(ValidationError, match="proxyType, proxyAddress"): + task.to_dict() + + +def test_create_task_rejects_non_task_value(): + client = CaptchaClient("api-key") + + with pytest.raises(ValidationError, match="task must be an instance of BaseTask"): + client.create_task(None) From cecf35f4e77ae2920db3b0caff6de2dcfdf4cd60 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:35:54 +0300 Subject: [PATCH 03/11] docs: clarify unavailable proxy task variants --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8695653..8cc528f 100644 --- a/README.md +++ b/README.md @@ -151,15 +151,18 @@ on the target page. |---|---|---| | reCAPTCHA v2 | ✅ | ✅ | | reCAPTCHA v2 Enterprise | ✅ | ✅ | -| reCAPTCHA v3 | ✅ | ❌ | +| reCAPTCHA v3 | ✅ | — | | Cloudflare Turnstile | ✅ | ✅ | | GeeTest v3 | ✅ | ✅ | | GeeTest v4 | ✅ | ✅ | -| Image to Text | ✅ | ❌ | +| Image to Text | ✅ | — | | Yandex SmartCaptcha | ✅ | ✅ | -| Coordinates (click captcha) | ✅ | ❌ | +| Coordinates (click captcha) | ✅ | — | | Tencent | ✅ | ✅ | +An em dash means that this CAPTCHA type has no separate task method that accepts +your own proxy; it does not indicate that solving the CAPTCHA is unsupported. + ## Client Reference Every method below is available on both `CaptchaClient` (sync, `requests`-based) and From bb4af82002ed748ef539a0222f8a692747b9e0ff Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:36:15 +0300 Subject: [PATCH 04/11] docs: explain py.typed marker --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8cc528f..c9a2a9d 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,10 @@ enabled in your editor, hover over `CaptchaClient`, `AsyncCaptchaClient`, their methods, or a task class such as `RecaptchaV2TaskProxyless` to read the documentation. Signature help shows argument names, types, and defaults while you type a call. +The `captcha_solver_api/py.typed` file is intentionally empty. It is the standard +[PEP 561](https://peps.python.org/pep-0561/) marker that tells type checkers to use +the inline type annotations shipped with this package. + Docstrings describe parameters, solution fields, exceptions, and usage examples. Task examples assume an existing synchronous `client`; with `AsyncCaptchaClient`, use `solution = await client.solve(task)` instead. Async snippets run inside an From 6b5a3fb54cac0ecc8453fbbac2efa0b5aa4d8cfb Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:38:05 +0300 Subject: [PATCH 05/11] docs: split proxy examples into separate files --- examples/README.md | 23 +++--- examples/async/geetest_v3_proxy.py | 76 +++++++++++++++++++ ...{geetest_v3.py => geetest_v3_proxyless.py} | 23 +----- examples/async/geetest_v4_proxy.py | 61 +++++++++++++++ ...{geetest_v4.py => geetest_v4_proxyless.py} | 25 +----- .../async/recaptcha_v2_enterprise_proxy.py | 62 +++++++++++++++ ...y => recaptcha_v2_enterprise_proxyless.py} | 29 +------ examples/async/recaptcha_v2_proxy.py | 44 +++++++++++ ...aptcha_v2.py => recaptcha_v2_proxyless.py} | 18 ++--- .../async/{tencent.py => tencent_proxy.py} | 21 +---- examples/async/tencent_proxyless.py | 54 +++++++++++++ examples/async/turnstile_proxy.py | 60 +++++++++++++++ .../{turnstile.py => turnstile_proxyless.py} | 27 +------ ...aptcha.py => yandex_smartcaptcha_proxy.py} | 22 +----- .../async/yandex_smartcaptcha_proxyless.py | 54 +++++++++++++ examples/sync/geetest_v3_proxy.py | 76 +++++++++++++++++++ ...{geetest_v3.py => geetest_v3_proxyless.py} | 22 +----- examples/sync/geetest_v4_proxy.py | 62 +++++++++++++++ ...{geetest_v4.py => geetest_v4_proxyless.py} | 24 +----- .../sync/recaptcha_v2_enterprise_proxy.py | 63 +++++++++++++++ ...y => recaptcha_v2_enterprise_proxyless.py} | 28 +------ ...{recaptcha_v2.py => recaptcha_v2_proxy.py} | 21 +---- examples/sync/recaptcha_v2_proxyless.py | 57 ++++++++++++++ .../sync/{tencent.py => tencent_proxy.py} | 21 +---- examples/sync/tencent_proxyless.py | 49 ++++++++++++ examples/sync/turnstile_proxy.py | 61 +++++++++++++++ .../{turnstile.py => turnstile_proxyless.py} | 26 +------ ...aptcha.py => yandex_smartcaptcha_proxy.py} | 22 +----- .../sync/yandex_smartcaptcha_proxyless.py | 49 ++++++++++++ 29 files changed, 860 insertions(+), 320 deletions(-) create mode 100644 examples/async/geetest_v3_proxy.py rename examples/async/{geetest_v3.py => geetest_v3_proxyless.py} (74%) create mode 100644 examples/async/geetest_v4_proxy.py rename examples/async/{geetest_v4.py => geetest_v4_proxyless.py} (66%) create mode 100644 examples/async/recaptcha_v2_enterprise_proxy.py rename examples/async/{recaptcha_v2_enterprise.py => recaptcha_v2_enterprise_proxyless.py} (60%) create mode 100644 examples/async/recaptcha_v2_proxy.py rename examples/async/{recaptcha_v2.py => recaptcha_v2_proxyless.py} (62%) rename examples/async/{tencent.py => tencent_proxy.py} (65%) create mode 100644 examples/async/tencent_proxyless.py create mode 100644 examples/async/turnstile_proxy.py rename examples/async/{turnstile.py => turnstile_proxyless.py} (63%) rename examples/async/{yandex_smartcaptcha.py => yandex_smartcaptcha_proxy.py} (65%) create mode 100644 examples/async/yandex_smartcaptcha_proxyless.py create mode 100644 examples/sync/geetest_v3_proxy.py rename examples/sync/{geetest_v3.py => geetest_v3_proxyless.py} (77%) create mode 100644 examples/sync/geetest_v4_proxy.py rename examples/sync/{geetest_v4.py => geetest_v4_proxyless.py} (70%) create mode 100644 examples/sync/recaptcha_v2_enterprise_proxy.py rename examples/sync/{recaptcha_v2_enterprise.py => recaptcha_v2_enterprise_proxyless.py} (63%) rename examples/sync/{recaptcha_v2.py => recaptcha_v2_proxy.py} (71%) create mode 100644 examples/sync/recaptcha_v2_proxyless.py rename examples/sync/{tencent.py => tencent_proxy.py} (65%) create mode 100644 examples/sync/tencent_proxyless.py create mode 100644 examples/sync/turnstile_proxy.py rename examples/sync/{turnstile.py => turnstile_proxyless.py} (67%) rename examples/sync/{yandex_smartcaptcha.py => yandex_smartcaptcha_proxy.py} (65%) create mode 100644 examples/sync/yandex_smartcaptcha_proxyless.py diff --git a/examples/README.md b/examples/README.md index 00057ac..ec2b645 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,21 +48,26 @@ Replace placeholder values such as `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`, `YOUR_CAPT - [`async`](async) contains asynchronous examples using `AsyncCaptchaClient`. - [`assets`](assets) contains sample images used by image and coordinate examples. -Each supported CAPTCHA type has a matching sync and async script: +Each supported CAPTCHA type has matching sync and async scripts. Types that can +use your own proxy have separate `*_proxyless.py` and `*_proxy.py` files so each +script demonstrates exactly one task variant. | CAPTCHA type | Sync | Async | Main result | |---|---|---|---| | Account balance | [balance.py](sync/balance.py) | [balance.py](async/balance.py) | `float` balance | -| reCAPTCHA v2 | [recaptcha_v2.py](sync/recaptcha_v2.py) | [recaptcha_v2.py](async/recaptcha_v2.py) | `gRecaptchaResponse` | -| reCAPTCHA v2 Enterprise | [recaptcha_v2_enterprise.py](sync/recaptcha_v2_enterprise.py) | [recaptcha_v2_enterprise.py](async/recaptcha_v2_enterprise.py) | `gRecaptchaResponse` | +| reCAPTCHA v2 (proxyless) | [recaptcha_v2_proxyless.py](sync/recaptcha_v2_proxyless.py) | [recaptcha_v2_proxyless.py](async/recaptcha_v2_proxyless.py) | `gRecaptchaResponse` | +| reCAPTCHA v2 (proxy) | [recaptcha_v2_proxy.py](sync/recaptcha_v2_proxy.py) | [recaptcha_v2_proxy.py](async/recaptcha_v2_proxy.py) | `gRecaptchaResponse` | +| reCAPTCHA v2 Enterprise (proxyless) | [recaptcha_v2_enterprise_proxyless.py](sync/recaptcha_v2_enterprise_proxyless.py) | [recaptcha_v2_enterprise_proxyless.py](async/recaptcha_v2_enterprise_proxyless.py) | `gRecaptchaResponse` | +| reCAPTCHA v2 Enterprise (proxy) | [recaptcha_v2_enterprise_proxy.py](sync/recaptcha_v2_enterprise_proxy.py) | [recaptcha_v2_enterprise_proxy.py](async/recaptcha_v2_enterprise_proxy.py) | `gRecaptchaResponse` | | reCAPTCHA v3 | [recaptcha_v3.py](sync/recaptcha_v3.py) | [recaptcha_v3.py](async/recaptcha_v3.py) | `gRecaptchaResponse` | -| Cloudflare Turnstile | [turnstile.py](sync/turnstile.py) | [turnstile.py](async/turnstile.py) | `token` | +| Cloudflare Turnstile (proxyless) | [turnstile_proxyless.py](sync/turnstile_proxyless.py) | [turnstile_proxyless.py](async/turnstile_proxyless.py) | `token` | +| Cloudflare Turnstile (proxy) | [turnstile_proxy.py](sync/turnstile_proxy.py) | [turnstile_proxy.py](async/turnstile_proxy.py) | `token` | | Image to Text | [image_to_text.py](sync/image_to_text.py) | [image_to_text.py](async/image_to_text.py) | `text` | -| GeeTest v3 | [geetest_v3.py](sync/geetest_v3.py) | [geetest_v3.py](async/geetest_v3.py) | `challenge`, `validate`, `seccode` | -| GeeTest v4 | [geetest_v4.py](sync/geetest_v4.py) | [geetest_v4.py](async/geetest_v4.py) | `captcha_output` and related fields | -| Yandex SmartCaptcha token | [yandex_smartcaptcha.py](sync/yandex_smartcaptcha.py) | [yandex_smartcaptcha.py](async/yandex_smartcaptcha.py) | `token` | +| GeeTest v3 (proxyless / proxy) | [proxyless](sync/geetest_v3_proxyless.py) / [proxy](sync/geetest_v3_proxy.py) | [proxyless](async/geetest_v3_proxyless.py) / [proxy](async/geetest_v3_proxy.py) | `challenge`, `validate`, `seccode` | +| GeeTest v4 (proxyless / proxy) | [proxyless](sync/geetest_v4_proxyless.py) / [proxy](sync/geetest_v4_proxy.py) | [proxyless](async/geetest_v4_proxyless.py) / [proxy](async/geetest_v4_proxy.py) | `captcha_output` and related fields | +| Yandex SmartCaptcha (proxyless / proxy) | [proxyless](sync/yandex_smartcaptcha_proxyless.py) / [proxy](sync/yandex_smartcaptcha_proxy.py) | [proxyless](async/yandex_smartcaptcha_proxyless.py) / [proxy](async/yandex_smartcaptcha_proxy.py) | `token` | | Coordinates / click captcha | [coordinates.py](sync/coordinates.py) | [coordinates.py](async/coordinates.py) | `coordinates` | -| Tencent | [tencent.py](sync/tencent.py) | [tencent.py](async/tencent.py) | `appid`, `ret`, `ticket`, `randstr` | +| Tencent (proxyless / proxy) | [proxyless](sync/tencent_proxyless.py) / [proxy](sync/tencent_proxy.py) | [proxyless](async/tencent_proxyless.py) / [proxy](async/tencent_proxy.py) | `appid`, `ret`, `ticket`, `randstr` | ## Running an example @@ -71,7 +76,7 @@ Run commands from the repository root so local imports and bundled assets resolv ```bash python examples/sync/balance.py python examples/sync/image_to_text.py -python examples/async/recaptcha_v2.py +python examples/async/recaptcha_v2_proxyless.py ``` Async scripts create an `AsyncCaptchaClient` and await the same operations as the synchronous client. For several captchas, see the parallel solving pattern in the main [README](../README.md#solving-multiple-captchas-in-parallel). diff --git a/examples/async/geetest_v3_proxy.py b/examples/async/geetest_v3_proxy.py new file mode 100644 index 0000000..1b55cf2 --- /dev/null +++ b/examples/async/geetest_v3_proxy.py @@ -0,0 +1,76 @@ +""" +Example: Solve a GeeTest v3 challenge with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL, gt, and challenge with values from your target page. + Important: the challenge value is dynamic. Fetch a fresh one for each request. + +NOTE: "https://target-site.com/path/to/geetest/init" below is a PLACEHOLDER, not a +real endpoint -- this script will not run end-to-end as-is. Replace it with a request +to your actual target page (or wherever it exposes a fresh `challenge` value) before +running this example. It's here only to illustrate where that fetch belongs in the flow. +""" + +import asyncio +import os +import sys + +import httpx + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import GeeTestTask + +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) + + +async def main(): + """ + Important: the value of the 'challenge' parameter is dynamic. + For each request to the API you need to get a new value from the target page. + Below is an example of fetching it from a demo endpoint. + """ + # Fetch a fresh challenge value from the target page. + # 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"] + + # --- 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)) + except Exception as e: + sys.exit(e) + + +asyncio.run(main()) diff --git a/examples/async/geetest_v3.py b/examples/async/geetest_v3_proxyless.py similarity index 74% rename from examples/async/geetest_v3.py rename to examples/async/geetest_v3_proxyless.py index 1443530..884da05 100644 --- a/examples/async/geetest_v3.py +++ b/examples/async/geetest_v3_proxyless.py @@ -31,7 +31,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless +from captcha_solver_api.tasks import GeeTestTaskProxyless api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -73,25 +73,4 @@ async def main(): 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)) - except Exception as e: - sys.exit(e) - - asyncio.run(main()) diff --git a/examples/async/geetest_v4_proxy.py b/examples/async/geetest_v4_proxy.py new file mode 100644 index 0000000..5d9dad3 --- /dev/null +++ b/examples/async/geetest_v4_proxy.py @@ -0,0 +1,61 @@ +""" +Example: Solve a GeeTest v4 challenge with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and captcha_id with values from your target page. + GeeTest v4 drops gt/challenge entirely. It uses captcha_id instead, which + (unlike v3's challenge) is static per site, so it doesn't need to be + re-fetched for every request. +""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import GeeTestTask + +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) + + +async def main(): + # --- 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)) + except Exception as e: + sys.exit(e) + + +asyncio.run(main()) diff --git a/examples/async/geetest_v4.py b/examples/async/geetest_v4_proxyless.py similarity index 66% rename from examples/async/geetest_v4.py rename to examples/async/geetest_v4_proxyless.py index 0c5f002..f4c37dd 100644 --- a/examples/async/geetest_v4.py +++ b/examples/async/geetest_v4_proxyless.py @@ -26,7 +26,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless +from captcha_solver_api.tasks import GeeTestTaskProxyless api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -56,27 +56,4 @@ async def main(): 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)) - except Exception as e: - sys.exit(e) - - asyncio.run(main()) diff --git a/examples/async/recaptcha_v2_enterprise_proxy.py b/examples/async/recaptcha_v2_enterprise_proxy.py new file mode 100644 index 0000000..23ba4a3 --- /dev/null +++ b/examples/async/recaptcha_v2_enterprise_proxy.py @@ -0,0 +1,62 @@ +""" +Example: Solve a reCAPTCHA v2 Enterprise challenge with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. + If the site uses enterprisePayload, extract and pass it or the token may be rejected. +""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = AsyncCaptchaClient(api_key) + + +async def main(): + # --- With proxy example --- + # 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 + ) + ) + # Solution contains the same gRecaptchaResponse token. + print("result: " + str(result)) + except Exception as e: + sys.exit(e) + + +asyncio.run(main()) diff --git a/examples/async/recaptcha_v2_enterprise.py b/examples/async/recaptcha_v2_enterprise_proxyless.py similarity index 60% rename from examples/async/recaptcha_v2_enterprise.py rename to examples/async/recaptcha_v2_enterprise_proxyless.py index aabb7a8..7cd134a 100644 --- a/examples/async/recaptcha_v2_enterprise.py +++ b/examples/async/recaptcha_v2_enterprise_proxyless.py @@ -24,7 +24,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask, RecaptchaV2EnterpriseTaskProxyless +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTaskProxyless api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -56,31 +56,4 @@ async def main(): except Exception as e: sys.exit(e) - # --- With proxy example --- - # 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 - ) - ) - # Solution contains the same gRecaptchaResponse token. - print("result: " + str(result)) - except Exception as e: - sys.exit(e) - - asyncio.run(main()) diff --git a/examples/async/recaptcha_v2_proxy.py b/examples/async/recaptcha_v2_proxy.py new file mode 100644 index 0000000..0eaf97c --- /dev/null +++ b/examples/async/recaptcha_v2_proxy.py @@ -0,0 +1,44 @@ +"""Example: Solve reCAPTCHA v2 through your own proxy using the async client.""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import RecaptchaV2Task + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") +solver = AsyncCaptchaClient(api_key) + + +async def main(): + task = RecaptchaV2Task( + websiteURL="https://example.com/login", + websiteKey="YOUR_WEBSITE_KEY", + proxyType="http", + proxyAddress="1.2.3.4", + proxyPort=8080, + proxyLogin="user", + proxyPassword="password", + ) + try: + result = await solver.solve(task) + except Exception as exc: + sys.exit(str(exc)) + else: + print("result: " + str(result)) + + +asyncio.run(main()) diff --git a/examples/async/recaptcha_v2.py b/examples/async/recaptcha_v2_proxyless.py similarity index 62% rename from examples/async/recaptcha_v2.py rename to examples/async/recaptcha_v2_proxyless.py index ddbbd27..3206993 100644 --- a/examples/async/recaptcha_v2.py +++ b/examples/async/recaptcha_v2_proxyless.py @@ -1,10 +1,4 @@ -""" -Example: Solve reCAPTCHA v2 with the async client. - -Prerequisites: - Set the CAPTCHA_API_KEY environment variable. - httpx must be installed (it's a package dependency, so `pip install captcha-solver-api` covers it). -""" +"""Example: Solve reCAPTCHA v2 without your own proxy using the async client.""" import asyncio import os @@ -26,20 +20,18 @@ from captcha_solver_api.tasks import RecaptchaV2TaskProxyless 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", + websiteKey="YOUR_WEBSITE_KEY", ) - try: result = await solver.solve(task) - except Exception as e: - sys.exit(str(e)) + except Exception as exc: + sys.exit(str(exc)) else: print("result: " + str(result)) diff --git a/examples/async/tencent.py b/examples/async/tencent_proxy.py similarity index 65% rename from examples/async/tencent.py rename to examples/async/tencent_proxy.py index 28c9df3..fa176b2 100644 --- a/examples/async/tencent.py +++ b/examples/async/tencent_proxy.py @@ -24,7 +24,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import TencentTask, TencentTaskProxyless +from captcha_solver_api.tasks import TencentTask api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -32,25 +32,6 @@ async def main(): - # --- Proxyless example --- - # Solves Tencent captcha without a proxy. - # 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 - ) - ) - # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} - # Pass all four values together into the page's captcha callback as-is. - print("result: " + str(result)) - except Exception as e: - sys.exit(e) - # --- With proxy example --- # Solves Tencent captcha through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. diff --git a/examples/async/tencent_proxyless.py b/examples/async/tencent_proxyless.py new file mode 100644 index 0000000..6d5953f --- /dev/null +++ b/examples/async/tencent_proxyless.py @@ -0,0 +1,54 @@ +""" +Example: Solve a Tencent captcha challenge with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and appId with values from your target page. + Pass captchaScript if the site uses a non-default script URL. +""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import TencentTaskProxyless + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = AsyncCaptchaClient(api_key) + + +async def main(): + # --- Proxyless example --- + # Solves Tencent captcha without a proxy. + # 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 + ) + ) + # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} + # Pass all four values together into the page's captcha callback as-is. + print("result: " + str(result)) + except Exception as e: + sys.exit(e) + +asyncio.run(main()) diff --git a/examples/async/turnstile_proxy.py b/examples/async/turnstile_proxy.py new file mode 100644 index 0000000..a88b3b6 --- /dev/null +++ b/examples/async/turnstile_proxy.py @@ -0,0 +1,60 @@ +""" +Example: Solve a Cloudflare Turnstile challenge with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. + For Cloudflare Challenge pages, also extract and pass action, data, and pagedata. +""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import TurnstileTask + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = AsyncCaptchaClient(api_key) + + +async def main(): + # --- 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)) + except Exception as e: + sys.exit(e) + + +asyncio.run(main()) diff --git a/examples/async/turnstile.py b/examples/async/turnstile_proxyless.py similarity index 63% rename from examples/async/turnstile.py rename to examples/async/turnstile_proxyless.py index 40f9154..7414b71 100644 --- a/examples/async/turnstile.py +++ b/examples/async/turnstile_proxyless.py @@ -24,7 +24,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import TurnstileTask, TurnstileTaskProxyless +from captcha_solver_api.tasks import TurnstileTaskProxyless api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -54,29 +54,4 @@ async def main(): 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)) - except Exception as e: - sys.exit(e) - - asyncio.run(main()) diff --git a/examples/async/yandex_smartcaptcha.py b/examples/async/yandex_smartcaptcha_proxy.py similarity index 65% rename from examples/async/yandex_smartcaptcha.py rename to examples/async/yandex_smartcaptcha_proxy.py index 698eec5..e9bfe65 100644 --- a/examples/async/yandex_smartcaptcha.py +++ b/examples/async/yandex_smartcaptcha_proxy.py @@ -23,7 +23,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import AsyncCaptchaClient -from captcha_solver_api.tasks import YandexSmartCaptchaTask, YandexSmartCaptchaTaskProxyless +from captcha_solver_api.tasks import YandexSmartCaptchaTask api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") @@ -31,26 +31,6 @@ async def main(): - # --- Proxyless example --- - # Solves Yandex SmartCaptcha without a proxy. - # 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 - ) - ) - # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} - # Use solution.token in the smart-token field or pass to your site's backend. - print("result: " + str(result)) - except Exception as e: - sys.exit(e) - # --- With proxy example --- # Solves Yandex SmartCaptcha through your own proxy. # Note: this is the only captcha type where an https proxy is accepted. diff --git a/examples/async/yandex_smartcaptcha_proxyless.py b/examples/async/yandex_smartcaptcha_proxyless.py new file mode 100644 index 0000000..3f2dd04 --- /dev/null +++ b/examples/async/yandex_smartcaptcha_proxyless.py @@ -0,0 +1,54 @@ +""" +Example: Solve a Yandex SmartCaptcha challenge (token-based) with the async client. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. +""" + +import asyncio +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import AsyncCaptchaClient +from captcha_solver_api.tasks import YandexSmartCaptchaTaskProxyless + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = AsyncCaptchaClient(api_key) + + +async def main(): + # --- Proxyless example --- + # Solves Yandex SmartCaptcha without a proxy. + # 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 + ) + ) + # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} + # Use solution.token in the smart-token field or pass to your site's backend. + print("result: " + str(result)) + except Exception as e: + sys.exit(e) + +asyncio.run(main()) diff --git a/examples/sync/geetest_v3_proxy.py b/examples/sync/geetest_v3_proxy.py new file mode 100644 index 0000000..e91b441 --- /dev/null +++ b/examples/sync/geetest_v3_proxy.py @@ -0,0 +1,76 @@ +""" +Example: Solve a GeeTest v3 challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL, gt, and challenge with values from your target page. + Important: the challenge value is dynamic. Fetch a fresh one for each request. + +NOTE: "https://target-site.com/path/to/geetest/init" below is a PLACEHOLDER, not a +real endpoint -- this script will not run end-to-end as-is. Replace it with a request +to your actual target page (or wherever it exposes a fresh `challenge` value) before +running this example. It's here only to illustrate where that fetch belongs in the flow. +""" + +import os +import sys + +import requests + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import GeeTestTask + +# 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 +# set CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Windows +# 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") + +# Create a solver instance with your API key. +# GeeTest tasks may take longer. Increase timeout if needed. +solver = CaptchaClient(api_key, timeout=300, polling_interval=10) + +""" +Important: the value of the 'challenge' parameter is dynamic. +For each request to the API you need to get a new value from the target page. +Below is an example of fetching it from a demo endpoint. +""" +# Fetch a fresh challenge value from the target page. +# 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"] + +# --- 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)) +except Exception as e: + sys.exit(e) diff --git a/examples/sync/geetest_v3.py b/examples/sync/geetest_v3_proxyless.py similarity index 77% rename from examples/sync/geetest_v3.py rename to examples/sync/geetest_v3_proxyless.py index 199479e..10503e6 100644 --- a/examples/sync/geetest_v3.py +++ b/examples/sync/geetest_v3_proxyless.py @@ -30,7 +30,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless +from captcha_solver_api.tasks import 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 @@ -75,23 +75,3 @@ 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)) -except Exception as e: - sys.exit(e) diff --git a/examples/sync/geetest_v4_proxy.py b/examples/sync/geetest_v4_proxy.py new file mode 100644 index 0000000..9d05bdf --- /dev/null +++ b/examples/sync/geetest_v4_proxy.py @@ -0,0 +1,62 @@ +""" +Example: Solve a GeeTest v4 challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and captcha_id with values from your target page. + GeeTest v4 drops gt/challenge entirely. It uses captcha_id instead, which + (unlike v3's challenge) is static per site, so it doesn't need to be + re-fetched for every request. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import GeeTestTask + +# 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 +# set CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Windows +# 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") + +# Create a solver instance with your API key. +# GeeTest v4 tasks may take longer. Increase timeout if needed. +solver = CaptchaClient(api_key, timeout=300, polling_interval=10) + +# --- 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)) +except Exception as e: + sys.exit(e) diff --git a/examples/sync/geetest_v4.py b/examples/sync/geetest_v4_proxyless.py similarity index 70% rename from examples/sync/geetest_v4.py rename to examples/sync/geetest_v4_proxyless.py index dc066f0..55cb061 100644 --- a/examples/sync/geetest_v4.py +++ b/examples/sync/geetest_v4_proxyless.py @@ -25,7 +25,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless +from captcha_solver_api.tasks import 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 @@ -59,25 +59,3 @@ 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)) -except Exception as e: - sys.exit(e) diff --git a/examples/sync/recaptcha_v2_enterprise_proxy.py b/examples/sync/recaptcha_v2_enterprise_proxy.py new file mode 100644 index 0000000..b2e5ba3 --- /dev/null +++ b/examples/sync/recaptcha_v2_enterprise_proxy.py @@ -0,0 +1,63 @@ +""" +Example: Solve a reCAPTCHA v2 Enterprise challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. + If the site uses enterprisePayload, extract and pass it or the token may be rejected. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask + +# 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 +# set CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Windows +# 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") + +# Create a solver instance with your API key. +solver = CaptchaClient(api_key) + +# --- With proxy example --- +# 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 + ) + ) + # Solution contains the same gRecaptchaResponse token. + 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_proxyless.py similarity index 63% rename from examples/sync/recaptcha_v2_enterprise.py rename to examples/sync/recaptcha_v2_enterprise_proxyless.py index 9ac578a..bd7ebc5 100644 --- a/examples/sync/recaptcha_v2_enterprise.py +++ b/examples/sync/recaptcha_v2_enterprise_proxyless.py @@ -23,7 +23,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2EnterpriseTask, RecaptchaV2EnterpriseTaskProxyless +from captcha_solver_api.tasks import 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 @@ -59,29 +59,3 @@ print("result: " + str(result)) except Exception as e: sys.exit(e) - -# --- With proxy example --- -# 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 - ) - ) - # Solution contains the same gRecaptchaResponse token. - print("result: " + str(result)) -except Exception as e: - sys.exit(e) diff --git a/examples/sync/recaptcha_v2.py b/examples/sync/recaptcha_v2_proxy.py similarity index 71% rename from examples/sync/recaptcha_v2.py rename to examples/sync/recaptcha_v2_proxy.py index 9e76fe5..40f6674 100644 --- a/examples/sync/recaptcha_v2.py +++ b/examples/sync/recaptcha_v2_proxy.py @@ -22,7 +22,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import RecaptchaV2Task, RecaptchaV2TaskProxyless +from captcha_solver_api.tasks import RecaptchaV2Task # 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,25 +37,6 @@ # Optional: polling_interval (seconds before and between status checks, default 10) solver = CaptchaClient(api_key) -# --- Proxyless example --- -# Solves reCAPTCHA v2 without a proxy. -# The service uses its own IP addresses. -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 - ) - ) - # Solution contains {"gRecaptchaResponse": "03AGdBq..."} - # Pass this token to the g-recaptcha-response field or widget callback. - print("result: " + str(result)) -except Exception as e: - sys.exit(e) - # --- With proxy example --- # Solves reCAPTCHA v2 through your own proxy. # Required when the target site is geo-restricted or you need session consistency. diff --git a/examples/sync/recaptcha_v2_proxyless.py b/examples/sync/recaptcha_v2_proxyless.py new file mode 100644 index 0000000..2670021 --- /dev/null +++ b/examples/sync/recaptcha_v2_proxyless.py @@ -0,0 +1,57 @@ +""" +Example: Solve a reCAPTCHA v2 challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import 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 +# set CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Windows +# 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") + +# Create a solver instance with your API key. +# Optional: timeout (max seconds to wait for solution, default 120) +# Optional: polling_interval (seconds before and between status checks, default 10) +solver = CaptchaClient(api_key) + +# --- Proxyless example --- +# Solves reCAPTCHA v2 without a proxy. +# The service uses its own IP addresses. +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 + ) + ) + # Solution contains {"gRecaptchaResponse": "03AGdBq..."} + # Pass this token to the g-recaptcha-response field or widget callback. + print("result: " + str(result)) +except Exception as e: + sys.exit(e) diff --git a/examples/sync/tencent.py b/examples/sync/tencent_proxy.py similarity index 65% rename from examples/sync/tencent.py rename to examples/sync/tencent_proxy.py index 86fe48d..1314907 100644 --- a/examples/sync/tencent.py +++ b/examples/sync/tencent_proxy.py @@ -23,31 +23,12 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TencentTask, TencentTaskProxyless +from captcha_solver_api.tasks import TencentTask api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = CaptchaClient(api_key) -# --- Proxyless example --- -# Solves Tencent captcha without a proxy. -# 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 - ) - ) - # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} - # Pass all four values together into the page's captcha callback as-is. - print("result: " + str(result)) -except Exception as e: - sys.exit(e) - # --- With proxy example --- # Solves Tencent captcha through your own proxy. # Use when the target site is geo-restricted or you need a consistent session. diff --git a/examples/sync/tencent_proxyless.py b/examples/sync/tencent_proxyless.py new file mode 100644 index 0000000..fd620bc --- /dev/null +++ b/examples/sync/tencent_proxyless.py @@ -0,0 +1,49 @@ +""" +Example: Solve a Tencent captcha challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and appId with values from your target page. + Pass captchaScript if the site uses a non-default script URL. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import TencentTaskProxyless + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = CaptchaClient(api_key) + +# --- Proxyless example --- +# Solves Tencent captcha without a proxy. +# 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 + ) + ) + # Solution contains {"appid": "...", "ret": 0, "ticket": "...", "randstr": "..."} + # Pass all four values together into the page's captcha callback as-is. + print("result: " + str(result)) +except Exception as e: + sys.exit(e) diff --git a/examples/sync/turnstile_proxy.py b/examples/sync/turnstile_proxy.py new file mode 100644 index 0000000..29d61ac --- /dev/null +++ b/examples/sync/turnstile_proxy.py @@ -0,0 +1,61 @@ +""" +Example: Solve a Cloudflare Turnstile challenge. + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. + For Cloudflare Challenge pages, also extract and pass action, data, and pagedata. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import TurnstileTask + +# 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 +# set CAPTCHA_API_KEY=1abc234de56fab7c89012d34e56fa7b8 on Windows +# 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") + +# Create a solver instance with your API key. +solver = CaptchaClient(api_key) + +# --- 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)) +except Exception as e: + sys.exit(e) diff --git a/examples/sync/turnstile.py b/examples/sync/turnstile_proxyless.py similarity index 67% rename from examples/sync/turnstile.py rename to examples/sync/turnstile_proxyless.py index bbc3743..258402d 100644 --- a/examples/sync/turnstile.py +++ b/examples/sync/turnstile_proxyless.py @@ -23,7 +23,7 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import TurnstileTask, TurnstileTaskProxyless +from captcha_solver_api.tasks import 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 @@ -57,27 +57,3 @@ 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)) -except Exception as e: - sys.exit(e) diff --git a/examples/sync/yandex_smartcaptcha.py b/examples/sync/yandex_smartcaptcha_proxy.py similarity index 65% rename from examples/sync/yandex_smartcaptcha.py rename to examples/sync/yandex_smartcaptcha_proxy.py index 11b328f..08a2fc5 100644 --- a/examples/sync/yandex_smartcaptcha.py +++ b/examples/sync/yandex_smartcaptcha_proxy.py @@ -22,32 +22,12 @@ load_dotenv(os.path.join(repo_root, ".env")) from captcha_solver_api import CaptchaClient -from captcha_solver_api.tasks import YandexSmartCaptchaTask, YandexSmartCaptchaTaskProxyless +from captcha_solver_api.tasks import YandexSmartCaptchaTask api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") solver = CaptchaClient(api_key) -# --- Proxyless example --- -# Solves Yandex SmartCaptcha without a proxy. -# 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 - ) - ) - # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} - # Use solution.token in the smart-token field or pass to your site's backend. - print("result: " + str(result)) -except Exception as e: - sys.exit(e) - # --- With proxy example --- # Solves Yandex SmartCaptcha through your own proxy. # Note: this is the only captcha type where an https proxy is accepted. diff --git a/examples/sync/yandex_smartcaptcha_proxyless.py b/examples/sync/yandex_smartcaptcha_proxyless.py new file mode 100644 index 0000000..af1b089 --- /dev/null +++ b/examples/sync/yandex_smartcaptcha_proxyless.py @@ -0,0 +1,49 @@ +""" +Example: Solve a Yandex SmartCaptcha challenge (token-based). + +Prerequisites: + Set the CAPTCHA_API_KEY environment variable. + Replace websiteURL and websiteKey with values from your target page. +""" + +import os +import sys + +try: + from dotenv import load_dotenv +except ModuleNotFoundError as exc: + 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")) + +from captcha_solver_api import CaptchaClient +from captcha_solver_api.tasks import YandexSmartCaptchaTaskProxyless + +api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY") + +solver = CaptchaClient(api_key) + +# --- Proxyless example --- +# Solves Yandex SmartCaptcha without a proxy. +# 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 + ) + ) + # Solution contains {"token": "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..."} + # Use solution.token in the smart-token field or pass to your site's backend. + print("result: " + str(result)) +except Exception as e: + sys.exit(e) From b36877c1497cc8ed8265910676c4e9472d2c24cd Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:38:32 +0300 Subject: [PATCH 06/11] docs: add security policy --- SECURITY.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..aea455c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,36 @@ +# Security Policy + +## Supported versions + +Security fixes are provided for the latest released version of +`captcha-solver-api`. Before reporting a vulnerability, verify that it is still +present after upgrading to the latest version available on PyPI. + +| Version | Supported | +|---|---| +| Latest release | Yes | +| Older releases | No | + +## Reporting a vulnerability + +Please report suspected vulnerabilities privately through +[GitHub Security Advisories](https://github.com/captcha-solver-api/python-sdk/security/advisories/new). +Do not open a public issue or pull request for an undisclosed vulnerability. + +Include the following information when possible: + +- the affected SDK version and Python version; +- a clear description of the impact; +- steps or a minimal example that reproduces the problem; +- any known mitigations or suggested fixes. + +You should receive an acknowledgement within five business days. We will keep +you informed while the report is investigated and will coordinate disclosure +after a fix is available. Please allow a reasonable amount of time for the +issue to be addressed before making it public. + +## Scope + +This policy covers the Python SDK in this repository. Vulnerabilities in the +Captcha Solver service or website should be reported through the security +channel of the corresponding project. From 413a2ea072dc35e9bea6abf25d0f9a747b42f5c3 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:40:18 +0300 Subject: [PATCH 07/11] chore: set version to 1.0.5 --- captcha_solver_api/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captcha_solver_api/_version.py b/captcha_solver_api/_version.py index 92192ee..68cdeee 100644 --- a/captcha_solver_api/_version.py +++ b/captcha_solver_api/_version.py @@ -1 +1 @@ -__version__ = "1.0.4" +__version__ = "1.0.5" From edaab93c6a4bcbe093e2f1d96de977815ded0e46 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:54:09 +0300 Subject: [PATCH 08/11] fix: format async proxyless examples --- examples/async/geetest_v3_proxyless.py | 1 + examples/async/geetest_v4_proxyless.py | 1 + examples/async/recaptcha_v2_enterprise_proxyless.py | 1 + examples/async/tencent_proxyless.py | 1 + examples/async/turnstile_proxyless.py | 1 + examples/async/yandex_smartcaptcha_proxyless.py | 1 + 6 files changed, 6 insertions(+) diff --git a/examples/async/geetest_v3_proxyless.py b/examples/async/geetest_v3_proxyless.py index 884da05..1759891 100644 --- a/examples/async/geetest_v3_proxyless.py +++ b/examples/async/geetest_v3_proxyless.py @@ -73,4 +73,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) diff --git a/examples/async/geetest_v4_proxyless.py b/examples/async/geetest_v4_proxyless.py index f4c37dd..8a91357 100644 --- a/examples/async/geetest_v4_proxyless.py +++ b/examples/async/geetest_v4_proxyless.py @@ -56,4 +56,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) diff --git a/examples/async/recaptcha_v2_enterprise_proxyless.py b/examples/async/recaptcha_v2_enterprise_proxyless.py index 7cd134a..133e94a 100644 --- a/examples/async/recaptcha_v2_enterprise_proxyless.py +++ b/examples/async/recaptcha_v2_enterprise_proxyless.py @@ -56,4 +56,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) diff --git a/examples/async/tencent_proxyless.py b/examples/async/tencent_proxyless.py index 6d5953f..f260fd3 100644 --- a/examples/async/tencent_proxyless.py +++ b/examples/async/tencent_proxyless.py @@ -51,4 +51,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) diff --git a/examples/async/turnstile_proxyless.py b/examples/async/turnstile_proxyless.py index 7414b71..d59e8d4 100644 --- a/examples/async/turnstile_proxyless.py +++ b/examples/async/turnstile_proxyless.py @@ -54,4 +54,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) diff --git a/examples/async/yandex_smartcaptcha_proxyless.py b/examples/async/yandex_smartcaptcha_proxyless.py index 3f2dd04..8e980d6 100644 --- a/examples/async/yandex_smartcaptcha_proxyless.py +++ b/examples/async/yandex_smartcaptcha_proxyless.py @@ -51,4 +51,5 @@ async def main(): except Exception as e: sys.exit(e) + asyncio.run(main()) From bda2216f7fb4c136bdd42b72514e8bc182e2754a Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Wed, 28 Oct 2026 15:55:43 +0300 Subject: [PATCH 09/11] ci: run Python matrix on Ubuntu 22.04 --- .github/workflows/tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 16e814f..aeed544 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,7 +38,10 @@ jobs: # to catch packaging errors early. Skipped if lint fails to save CI minutes. test: needs: lint - runs-on: ubuntu-latest + # Python 3.9 is not available through setup-python on Ubuntu 26.04, + # which ubuntu-latest currently resolves to. Keep the compatibility + # matrix on an image that provides every supported Python version. + runs-on: ubuntu-22.04 strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] From ef6134ce603e1a43e8376cb43f4ed62adbdbfeca Mon Sep 17 00:00:00 2001 From: dzmitry-duboyski Date: Thu, 29 Oct 2026 01:25:54 +0300 Subject: [PATCH 10/11] fix: tighten validation and document required task parameters - reject whitespace-only client_key in both clients - document ValidationError in solve() and create_task() docstrings and README - mark every task parameter as Required or Optional in docstrings - replace em dash in proxy support table with a plain dash - update README references to renamed example files Co-Authored-By: Claude Opus 5.5 --- README.md | 23 +-- captcha_solver_api/async_client.py | 10 +- captcha_solver_api/client.py | 10 +- captcha_solver_api/tasks.py | 260 +++++++++++++-------------- examples/README.md | 2 +- tests/async/test_async_validation.py | 5 + tests/sync/test_validation.py | 5 + 7 files changed, 168 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index c9a2a9d..5f54dc3 100644 --- a/README.md +++ b/README.md @@ -155,16 +155,16 @@ on the target page. |---|---|---| | reCAPTCHA v2 | ✅ | ✅ | | reCAPTCHA v2 Enterprise | ✅ | ✅ | -| reCAPTCHA v3 | ✅ | — | +| reCAPTCHA v3 | ✅ | - | | Cloudflare Turnstile | ✅ | ✅ | | GeeTest v3 | ✅ | ✅ | | GeeTest v4 | ✅ | ✅ | -| Image to Text | ✅ | — | +| Image to Text | ✅ | - | | Yandex SmartCaptcha | ✅ | ✅ | -| Coordinates (click captcha) | ✅ | — | +| Coordinates (click captcha) | ✅ | - | | Tencent | ✅ | ✅ | -An em dash means that this CAPTCHA type has no separate task method that accepts +A dash (`-`) means that this CAPTCHA type has no separate task class that accepts your own proxy; it does not indicate that solving the CAPTCHA is unsupported. ## Client Reference @@ -214,14 +214,17 @@ solution -- wraps `create_task()` + `get_task_result()` so you don't poll by han Returns the `solution` dict once `status` is `"ready"` -- its shape depends on the task type (see [Captcha Types](#captcha-types)). -Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`. +Raises `ValidationError` before any request if `task` is not a task object or a +required task parameter is missing or empty. Otherwise raises `ApiError`, +`CaptchaTimeoutError`, or `NetworkError`. ### `create_task(task, language_pool=None)` Submits `task` and returns its numeric task ID without waiting for a solution. Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to manage polling yourself (e.g. checking on many tasks from a different process). -Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`. +Raises `ValidationError` before any request if `task` is invalid (same rules as +`solve()`). Otherwise raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`. ### `get_task_result(task_id)` @@ -486,7 +489,7 @@ print(result["validate"]) print(result["seccode"]) ``` `challenge` is session-specific and expires quickly, so it can't be hardcoded -into a static example -- see [examples/sync/geetest_v3.py](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/sync/geetest_v3.py) +into a static example -- see [examples/sync/geetest_v3_proxyless.py](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/sync/geetest_v3_proxyless.py) for where the fetch belongs in the flow. ```python @@ -729,12 +732,12 @@ sync/async example list, setup steps, expected results, and placeholder guidance `CAPTCHA_API_KEY` -- they read sample images bundled in [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed. `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file. -- **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`, - `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use +- **Token captchas** (`recaptcha_v2_*.py`, `recaptcha_v2_enterprise_*.py`, `recaptcha_v3.py`, + `turnstile_*.py`, `yandex_smartcaptcha_*.py`, `geetest_v4_*.py`, `tencent_*.py`) use placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`, `YOUR_CAPTCHA_ID`) -- replace these with the real values from your own target page before running. -- **`geetest_v3.py`** additionally needs `challenge` fetched fresh for every +- **`geetest_v3_*.py`** additionally needs `challenge` fetched fresh for every request -- it's single-use and expires within seconds, so it can't be hardcoded into a static example. `"https://target-site.com/path/to/geetest/init"` is a placeholder; replace it with a request to your own target's equivalent diff --git a/captcha_solver_api/async_client.py b/captcha_solver_api/async_client.py index 0638f0e..90267bf 100644 --- a/captcha_solver_api/async_client.py +++ b/captcha_solver_api/async_client.py @@ -43,7 +43,7 @@ class AsyncCaptchaClient: the account default. Can be overridden in `solve()` or `create_task()`. Raises: - ValidationError: `client_key` is empty. + ValidationError: `client_key` is missing, empty, or whitespace. Example: from captcha_solver_api import AsyncCaptchaClient @@ -85,9 +85,9 @@ def __init__( pool. Raises: - ValidationError: `client_key` is empty. + ValidationError: `client_key` is missing, empty, or whitespace. """ - if not client_key: + if not isinstance(client_key, str) or not client_key.strip(): raise ValidationError("client_key is required") self.client_key = client_key @@ -166,6 +166,8 @@ async def create_task(self, task: Any, language_pool: Optional[str] = None) -> i The numeric task ID to pass to `get_task_result()`. Raises: + ValidationError: `task` is not a task object, or a required task + parameter is missing or empty. Raised before any request is sent. ApiError: The API rejected the task or its parameters. NetworkError: The request failed at the transport level. CaptchaTimeoutError: The HTTP request timed out. @@ -277,6 +279,8 @@ async def solve( or the README's method reference. Raises: + ValidationError: `task` is not a task object, or a required task + parameter is missing or empty. Raised before any request is sent. ApiError: The API rejected the task or reported a solving error. CaptchaTimeoutError: Polling exceeded its deadline or an HTTP request timed out. diff --git a/captcha_solver_api/client.py b/captcha_solver_api/client.py index 5eb45a6..a2fe4e5 100644 --- a/captcha_solver_api/client.py +++ b/captcha_solver_api/client.py @@ -33,7 +33,7 @@ class CaptchaClient: the account default. Can be overridden in `solve()` or `create_task()`. Raises: - ValidationError: `client_key` is empty. + ValidationError: `client_key` is missing, empty, or whitespace. Example: from captcha_solver_api import CaptchaClient @@ -75,9 +75,9 @@ def __init__( pool. Raises: - ValidationError: `client_key` is empty. + ValidationError: `client_key` is missing, empty, or whitespace. """ - if not client_key: + if not isinstance(client_key, str) or not client_key.strip(): raise ValidationError("client_key is required") self.client_key = client_key @@ -154,6 +154,8 @@ def create_task(self, task: Any, language_pool: Optional[str] = None) -> int: The numeric task ID to pass to `get_task_result()`. Raises: + ValidationError: `task` is not a task object, or a required task + parameter is missing or empty. Raised before any request is sent. ApiError: The API rejected the task (bad key, bad parameters, etc). NetworkError: The request failed at the transport level. CaptchaTimeoutError: The HTTP request itself timed out (not the solve). @@ -255,6 +257,8 @@ def solve( or the README's method reference. Raises: + ValidationError: `task` is not a task object, or a required task + parameter is missing or empty. Raised before any request is sent. ApiError: The API rejected the task or reported an error while solving. CaptchaTimeoutError: Polling exceeded its deadline or an HTTP request timed out. diff --git a/captcha_solver_api/tasks.py b/captcha_solver_api/tasks.py index 871844e..0184719 100644 --- a/captcha_solver_api/tasks.py +++ b/captcha_solver_api/tasks.py @@ -52,11 +52,11 @@ class ProxyMixin: """Mixin that adds proxy fields to a task. Args (via `set_proxy`, or pass directly to the task's `__init__`): - proxy_type: `"http"`, `"socks4"`, or `"socks5"`. - proxy_address: Proxy IP address or hostname. - proxy_port: Proxy port. - proxy_login: Proxy auth username, if required. - proxy_password: Proxy auth password, if required. + proxy_type: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxy_address: Required. Proxy IP address or hostname. + proxy_port: Required. Proxy port. + proxy_login: Optional. Proxy auth username, if required. + proxy_password: Optional. Proxy auth password, if required. """ proxyType: str @@ -76,11 +76,11 @@ def set_proxy( """Sets the proxy fields on this task. Args: - proxy_type: `"http"`, `"socks4"`, or `"socks5"`. - proxy_address: Proxy IP address or hostname. - proxy_port: Proxy port. - proxy_login: Proxy auth username, if required. - proxy_password: Proxy auth password, if required. + proxy_type: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxy_address: Required. Proxy IP address or hostname. + proxy_port: Required. Proxy port. + proxy_login: Optional. Proxy auth username, if required. + proxy_password: Optional. Proxy auth password, if required. """ self.proxyType = proxy_type self.proxyAddress = proxy_address @@ -93,14 +93,14 @@ class RecaptchaV2TaskProxyless(BaseTask): """reCAPTCHA v2 without proxy -- the service solves using its own IPs. Args: - websiteURL: Full URL of the page where the captcha is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - isInvisible: `True` for invisible reCAPTCHA v2. - recaptchaDataSValue: The `data-s` value, found on Google Search/YouTube pages. - apiDomain: Non-default domain the widget's script is served from, if any. - userAgent: User-Agent to solve with. Recommended to match the agent that + websiteURL: Required. Full URL of the page where the captcha is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + isInvisible: Optional. `True` for invisible reCAPTCHA v2. + recaptchaDataSValue: Optional. The `data-s` value, found on Google Search/YouTube pages. + apiDomain: Optional. Non-default domain the widget's script is served from, if any. + userAgent: Optional. User-Agent to solve with. Recommended to match the agent that will submit the resulting token. - cookies: Session cookies to use while solving, if the page requires them. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `gRecaptchaResponse` -- the token to submit as `g-recaptcha-response`. @@ -148,18 +148,18 @@ class RecaptchaV2Task(BaseTask, ProxyMixin): browser/bot that will submit the token. Args: - websiteURL: Full URL of the page where the captcha is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - isInvisible: `True` for invisible reCAPTCHA v2. - recaptchaDataSValue: The `data-s` value, found on Google Search/YouTube pages. - apiDomain: Non-default domain the widget's script is served from, if any. - userAgent: User-Agent to solve with. - cookies: Session cookies to use while solving, if the page requires them. + websiteURL: Required. Full URL of the page where the captcha is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + isInvisible: Optional. `True` for invisible reCAPTCHA v2. + recaptchaDataSValue: Optional. The `data-s` value, found on Google Search/YouTube pages. + apiDomain: Optional. Non-default domain the widget's script is served from, if any. + userAgent: Optional. User-Agent to solve with. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `gRecaptchaResponse` -- the token to submit as `g-recaptcha-response`. @@ -216,14 +216,14 @@ class RecaptchaV2EnterpriseTaskProxyless(BaseTask): """reCAPTCHA v2 Enterprise without proxy. Args: - websiteURL: Full URL of the page where the captcha is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - enterprisePayload: Extra parameters passed to `grecaptcha.enterprise.render` + websiteURL: Required. Full URL of the page where the captcha is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + enterprisePayload: Optional. Extra parameters passed to `grecaptcha.enterprise.render` on the page, if any (e.g. `{"s": "..."}`). - isInvisible: `True` for invisible reCAPTCHA v2 Enterprise. - apiDomain: Non-default domain, defaults to `google.com`. - userAgent: User-Agent to solve with. - cookies: Session cookies to use while solving, if the page requires them. + isInvisible: Optional. `True` for invisible reCAPTCHA v2 Enterprise. + apiDomain: Optional. Non-default domain, defaults to `google.com`. + userAgent: Optional. User-Agent to solve with. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `gRecaptchaResponse` -- the token to submit as `g-recaptcha-response`. @@ -267,19 +267,19 @@ class RecaptchaV2EnterpriseTask(BaseTask, ProxyMixin): """reCAPTCHA v2 Enterprise solved through your own proxy. Args: - websiteURL: Full URL of the page where the captcha is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - enterprisePayload: Extra parameters passed to `grecaptcha.enterprise.render` + websiteURL: Required. Full URL of the page where the captcha is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + enterprisePayload: Optional. Extra parameters passed to `grecaptcha.enterprise.render` on the page, if any (e.g. `{"s": "..."}`). - isInvisible: `True` for invisible reCAPTCHA v2 Enterprise. - apiDomain: Non-default domain, defaults to `google.com`. - userAgent: User-Agent to solve with. - cookies: Session cookies to use while solving, if the page requires them. + isInvisible: Optional. `True` for invisible reCAPTCHA v2 Enterprise. + apiDomain: Optional. Non-default domain, defaults to `google.com`. + userAgent: Optional. User-Agent to solve with. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `gRecaptchaResponse` -- the token to submit as `g-recaptcha-response`. @@ -337,12 +337,12 @@ class RecaptchaV3TaskProxyless(BaseTask): and invisible, so there's no widget/session to keep pinned to a proxy IP. Args: - websiteURL: Full URL of the page where the captcha is located. - websiteKey: Site key for the v3 widget. - minScore: Minimum acceptable token score to return, e.g. `0.3`, `0.7`, `0.9`. - pageAction: The `action` parameter passed to `grecaptcha.execute()` on the page. - isEnterprise: `True` if this is reCAPTCHA v3 Enterprise. - apiDomain: Non-standard domain the widget's script is served from, if any. + websiteURL: Required. Full URL of the page where the captcha is located. + websiteKey: Required. Site key for the v3 widget. + minScore: Required. Minimum acceptable token score to return, e.g. `0.3`, `0.7`, `0.9`. + pageAction: Optional. The `action` parameter passed to `grecaptcha.execute()` on the page. + isEnterprise: Optional. `True` if this is reCAPTCHA v3 Enterprise. + apiDomain: Optional. Non-standard domain the widget's script is served from, if any. Returns (`solution` from `solve()`): `gRecaptchaResponse` -- the token to submit as `g-recaptcha-response`. @@ -386,15 +386,15 @@ class TurnstileTaskProxyless(BaseTask): """Cloudflare Turnstile without proxy. Args: - websiteURL: Full URL of the page where the widget is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - action: Value of the widget's `data-action` attribute, if set. - data: Custom payload from the widget's `data-cdata` attribute, if set. - pagedata: Value of the `chlPageData` parameter, needed for some Cloudflare + websiteURL: Required. Full URL of the page where the widget is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + action: Optional. Value of the widget's `data-action` attribute, if set. + data: Optional. Custom payload from the widget's `data-cdata` attribute, if set. + pagedata: Optional. Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. Named lowercase (not `pageData`) to match the API field name exactly -- Cloudflare-specific fields are the one place this API doesn't camelCase. - userAgent: User-Agent of the browser that loaded the challenge. Pass it + userAgent: Optional. User-Agent of the browser that loaded the challenge. Pass it together with `action`, `data`, and `pagedata` for Cloudflare Challenge pages. It is not needed for a standalone widget. @@ -440,19 +440,19 @@ class TurnstileTask(BaseTask, ProxyMixin): """Cloudflare Turnstile solved through your own proxy. Args: - websiteURL: Full URL of the page where the widget is located. - websiteKey: Value of the widget's `data-sitekey` attribute. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - action: Value of the widget's `data-action` attribute, if set. - data: Custom payload from the widget's `data-cdata` attribute, if set. - pagedata: Value of the `chlPageData` parameter, needed for some Cloudflare + websiteURL: Required. Full URL of the page where the widget is located. + websiteKey: Required. Value of the widget's `data-sitekey` attribute. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + action: Optional. Value of the widget's `data-action` attribute, if set. + data: Optional. Custom payload from the widget's `data-cdata` attribute, if set. + pagedata: Optional. Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. Named lowercase (not `pageData`) to match the API field name exactly. - userAgent: User-Agent of the browser that loaded the challenge. Pass it + userAgent: Optional. User-Agent of the browser that loaded the challenge. Pass it for Cloudflare Challenge pages; it is not needed for a standalone widget. @@ -513,17 +513,17 @@ class ImageToTextTask(BaseTask): session or target site is involved. Args: - body: The captcha image, base64-encoded (no `data:image/...;base64,` prefix). - phrase: `True` if the answer is multiple words. - case: `True` if the answer is case-sensitive. - numeric: Character set hint -- `0` unspecified, `1` digits only, `2` letters + body: Required. The captcha image, base64-encoded (no `data:image/...;base64,` prefix). + phrase: Optional. `True` if the answer is multiple words. + case: Optional. `True` if the answer is case-sensitive. + numeric: Optional. Character set hint -- `0` unspecified, `1` digits only, `2` letters only, `3` any with digits, `4` any with letters. - math: `True` if the image contains a math expression to evaluate rather + math: Optional. `True` if the image contains a math expression to evaluate rather than text to transcribe. - minLength: Minimum expected answer length. - maxLength: Maximum expected answer length. - comment: Free-text hint for the worker (e.g. what kind of answer is expected). - imgInstructions: Optional supplementary instruction image, base64-encoded. + minLength: Optional. Minimum expected answer length. + maxLength: Optional. Maximum expected answer length. + comment: Optional. Free-text hint for the worker (e.g. what kind of answer is expected). + imgInstructions: Optional. Optional supplementary instruction image, base64-encoded. Returns (`solution` from `solve()`): `text` -- the recognized text/answer. @@ -572,16 +572,16 @@ class GeeTestTaskProxyless(BaseTask): instead. Args: - websiteURL: Full URL of the page where the widget is located. - version: `3` (default) or `4`. - gt: Public key of the GeeTest widget. Required for v3. - challenge: Session-specific challenge value from the page. Required for + websiteURL: Required. Full URL of the page where the widget is located. + version: Optional. `3` (default) or `4`. + gt: Optional. Public key of the GeeTest widget. Required for v3. + challenge: Optional. Session-specific challenge value from the page. Required for v3, and must be freshly fetched for every request -- it cannot be reused. - initParameters: Extra parameters from the page's `initGeetest` call. For + initParameters: Optional. Extra parameters from the page's `initGeetest` call. For v4, must contain `captcha_id`. - geetestApiServerSubdomain: Custom GeeTest API subdomain, if the site uses one. - userAgent: User-Agent to solve with. - risk_type: Value of the `risk_type` parameter from the captcha-loading + geetestApiServerSubdomain: Optional. Custom GeeTest API subdomain, if the site uses one. + userAgent: Optional. User-Agent to solve with. + risk_type: Optional. Value of the `risk_type` parameter from the captcha-loading request, if present. Dynamic, single-use, and time-limited. Returns (`solution` from `solve()`): @@ -631,21 +631,21 @@ class GeeTestTask(BaseTask, ProxyMixin): for the version-specific fields. Args: - websiteURL: Full URL of the page where the widget is located. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - version: `3` (default) or `4`. - gt: Public key of the GeeTest widget. Required for v3. - challenge: Session-specific challenge value from the page, must be fresh. + websiteURL: Required. Full URL of the page where the widget is located. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + version: Optional. `3` (default) or `4`. + gt: Optional. Public key of the GeeTest widget. Required for v3. + challenge: Optional. Session-specific challenge value from the page, must be fresh. Required for v3. - initParameters: Extra parameters from the page's `initGeetest` call. For + initParameters: Optional. Extra parameters from the page's `initGeetest` call. For v4, must contain `captcha_id`. - geetestApiServerSubdomain: Custom GeeTest API subdomain, if the site uses one. - userAgent: User-Agent to solve with. - risk_type: Value of the `risk_type` parameter from the captcha-loading + geetestApiServerSubdomain: Optional. Custom GeeTest API subdomain, if the site uses one. + userAgent: Optional. User-Agent to solve with. + risk_type: Optional. Value of the `risk_type` parameter from the captcha-loading request, if present. Dynamic, single-use, and time-limited. Returns (`solution` from `solve()`): @@ -707,10 +707,10 @@ class YandexSmartCaptchaTaskProxyless(BaseTask): """Yandex SmartCaptcha (token challenge) without proxy. Args: - websiteURL: Full URL of the page where the widget is located. - websiteKey: The `sitekey` value from the page source or captcha iframe. - userAgent: User-Agent to solve with. - cookies: Session cookies to use while solving, if the page requires them. + websiteURL: Required. Full URL of the page where the widget is located. + websiteKey: Required. The `sitekey` value from the page source or captcha iframe. + userAgent: Optional. User-Agent to solve with. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `token` -- the value to submit as the SmartCaptcha response token. @@ -748,15 +748,15 @@ class YandexSmartCaptchaTask(BaseTask, ProxyMixin): """Yandex SmartCaptcha (token challenge) solved through your own proxy. Args: - websiteURL: Full URL of the page where the widget is located. - websiteKey: The `sitekey` value from the page source or captcha iframe. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - userAgent: User-Agent to solve with. - cookies: Session cookies to use while solving, if the page requires them. + websiteURL: Required. Full URL of the page where the widget is located. + websiteKey: Required. The `sitekey` value from the page source or captcha iframe. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + userAgent: Optional. User-Agent to solve with. + cookies: Optional. Session cookies to use while solving, if the page requires them. Returns (`solution` from `solve()`): `token` -- the value to submit as the SmartCaptcha response token. @@ -808,12 +808,12 @@ class CoordinatesTask(BaseTask): is submitted directly. Args: - body: The captcha image, base64-encoded (no `data:image/...;base64,` prefix). - comment: Hint for the worker, e.g. `"click on the green apple"`. Recommended + body: Required. The captcha image, base64-encoded (no `data:image/...;base64,` prefix). + comment: Optional. Hint for the worker, e.g. `"click on the green apple"`. Recommended for generic click captchas. - imgInstructions: Optional instruction image, base64-encoded. - minClicks: Minimum number of clicks expected (default `1`). - maxClicks: Maximum number of clicks allowed. + imgInstructions: Optional. Optional instruction image, base64-encoded. + minClicks: Optional. Minimum number of clicks expected (default `1`). + maxClicks: Optional. Maximum number of clicks allowed. Returns (`solution` from `solve()`): `coordinates` -- a list of `{"x": int, "y": int}` pixel positions to click, @@ -854,9 +854,9 @@ class TencentTaskProxyless(BaseTask): """Tencent captcha without proxy. Args: - websiteURL: Full URL of the page where the captcha is located. - appId: Value of the `appId` parameter found in the page source. - captchaScript: URL of the Tencent captcha script, if the page uses a + websiteURL: Required. Full URL of the page where the captcha is located. + appId: Required. Value of the `appId` parameter found in the page source. + captchaScript: Optional. URL of the Tencent captcha script, if the page uses a non-default one. Returns (`solution` from `solve()`): @@ -894,14 +894,14 @@ class TencentTask(BaseTask, ProxyMixin): """Tencent captcha solved through your own proxy. Args: - websiteURL: Full URL of the page where the captcha is located. - appId: Value of the `appId` parameter found in the page source. - proxyType: `"http"`, `"socks4"`, or `"socks5"`. - proxyAddress: Proxy IP address or hostname. - proxyPort: Proxy port. - proxyLogin: Proxy auth username, if required. - proxyPassword: Proxy auth password, if required. - captchaScript: URL of the Tencent captcha script, if the page uses a + websiteURL: Required. Full URL of the page where the captcha is located. + appId: Required. Value of the `appId` parameter found in the page source. + proxyType: Required. `"http"`, `"socks4"`, or `"socks5"`. + proxyAddress: Required. Proxy IP address or hostname. + proxyPort: Required. Proxy port. + proxyLogin: Optional. Proxy auth username, if required. + proxyPassword: Optional. Proxy auth password, if required. + captchaScript: Optional. URL of the Tencent captcha script, if the page uses a non-default one. Returns (`solution` from `solve()`): diff --git a/examples/README.md b/examples/README.md index ec2b645..a7bd7f4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -85,7 +85,7 @@ Async scripts create an `AsyncCaptchaClient` and await the same operations as th - Image examples can use the bundled files in `examples/assets`; they do not require a target website. - Token examples use placeholder site keys and URLs. Replace them with values from a page you are authorized to test. -- `geetest_v3.py` needs a fresh `challenge` for every request; it expires quickly and must not be hardcoded. +- `geetest_v3_*.py` need a fresh `challenge` for every request; it expires quickly and must not be hardcoded. - Files using `*Task` instead of `*TaskProxyless` demonstrate solving through your own proxy. Supply valid proxy settings for your account. - For task parameters and response formats, see the [CAPTCHA type documentation](https://captcha-solver.com/en/docs/captcha-types). - For client methods and errors, see the [main README](../README.md#client-reference). diff --git a/tests/async/test_async_validation.py b/tests/async/test_async_validation.py index f07660a..9db1fcc 100644 --- a/tests/async/test_async_validation.py +++ b/tests/async/test_async_validation.py @@ -8,3 +8,8 @@ async def test_create_task_rejects_non_task_value(): async with AsyncCaptchaClient("api-key") as client: with pytest.raises(ValidationError, match="task must be an instance of BaseTask"): await client.create_task(None) + + +def test_async_client_rejects_whitespace_client_key(): + with pytest.raises(ValidationError, match="client_key is required"): + AsyncCaptchaClient(" ") diff --git a/tests/sync/test_validation.py b/tests/sync/test_validation.py index 2407116..6958a55 100644 --- a/tests/sync/test_validation.py +++ b/tests/sync/test_validation.py @@ -32,3 +32,8 @@ def test_create_task_rejects_non_task_value(): with pytest.raises(ValidationError, match="task must be an instance of BaseTask"): client.create_task(None) + + +def test_client_rejects_whitespace_client_key(): + with pytest.raises(ValidationError, match="client_key is required"): + CaptchaClient(" ") From e16259a075ff273a61d8e83b97e62f0f97d4f713 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Thu, 29 Oct 2026 13:23:43 +0300 Subject: [PATCH 11/11] fix: validate version-specific GeeTest parameters --- captcha_solver_api/tasks.py | 33 ++++++++++++++++++++ tests/async/test_async_validation.py | 30 ++++++++++++++++++ tests/sync/test_geetest.py | 46 +++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/captcha_solver_api/tasks.py b/captcha_solver_api/tasks.py index 0184719..73c8c60 100644 --- a/captcha_solver_api/tasks.py +++ b/captcha_solver_api/tasks.py @@ -46,6 +46,10 @@ def _validate_required_fields(self) -> None: if missing: fields = ", ".join(missing) raise ValidationError(f"Missing required task parameter(s): {fields}") + self._validate_specific_fields() + + def _validate_specific_fields(self) -> None: + """Validate task-specific requirements that depend on other fields.""" class ProxyMixin: @@ -566,6 +570,29 @@ def __init__( self.imgInstructions = imgInstructions +def _validate_geetest_fields(task: Any) -> None: + """Validate fields whose required status depends on the GeeTest version.""" + version = 3 if task.version is None else task.version + if version not in (3, 4): + raise ValidationError("GeeTest version must be 3 or 4") + + if version == 3: + missing = [ + name + for name in ("gt", "challenge") + if not isinstance(getattr(task, name, None), str) or not getattr(task, name).strip() + ] + if missing: + fields = ", ".join(missing) + raise ValidationError(f"Missing required GeeTest v3 parameter(s): {fields}") + return + + init_parameters = task.initParameters + captcha_id = init_parameters.get("captcha_id") if isinstance(init_parameters, dict) else None + if not isinstance(captcha_id, str) or not captcha_id.strip(): + raise ValidationError("Missing required GeeTest v4 parameter: initParameters.captcha_id") + + class GeeTestTaskProxyless(BaseTask): """GeeTest v3 or v4 without proxy. Set `version=4` for v4 (and provide `initParameters["captcha_id"]`); v3 is the default and needs `gt`/`challenge` @@ -605,6 +632,9 @@ class GeeTestTaskProxyless(BaseTask): type = "GeeTestTaskProxyless" + def _validate_specific_fields(self) -> None: + _validate_geetest_fields(self) + def __init__( self, websiteURL: str, @@ -672,6 +702,9 @@ class GeeTestTask(BaseTask, ProxyMixin): type = "GeeTestTask" + def _validate_specific_fields(self) -> None: + _validate_geetest_fields(self) + def __init__( self, websiteURL: str, diff --git a/tests/async/test_async_validation.py b/tests/async/test_async_validation.py index 9db1fcc..c53bae1 100644 --- a/tests/async/test_async_validation.py +++ b/tests/async/test_async_validation.py @@ -1,6 +1,9 @@ +from unittest.mock import AsyncMock, patch + import pytest from captcha_solver_api import AsyncCaptchaClient, ValidationError +from captcha_solver_api.tasks import GeeTestTaskProxyless @pytest.mark.asyncio @@ -13,3 +16,30 @@ async def test_create_task_rejects_non_task_value(): def test_async_client_rejects_whitespace_client_key(): with pytest.raises(ValidationError, match="client_key is required"): AsyncCaptchaClient(" ") + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "task, message", + [ + ( + GeeTestTaskProxyless(websiteURL="https://example.com"), + r"Missing required GeeTest v3 parameter\(s\): gt, challenge", + ), + ( + GeeTestTaskProxyless( + websiteURL="https://example.com", + version=4, + initParameters={}, + ), + r"Missing required GeeTest v4 parameter: initParameters\.captcha_id", + ), + ], +) +async def test_create_task_rejects_invalid_geetest_before_request(task, message): + async with AsyncCaptchaClient("api-key") as client: + with patch.object(client, "_request", new_callable=AsyncMock) as request: + with pytest.raises(ValidationError, match=message): + await client.create_task(task) + + request.assert_not_awaited() diff --git a/tests/sync/test_geetest.py b/tests/sync/test_geetest.py index 3e12023..9467a65 100644 --- a/tests/sync/test_geetest.py +++ b/tests/sync/test_geetest.py @@ -4,11 +4,55 @@ from unittest.mock import patch -from captcha_solver_api import CaptchaClient +import pytest + +from captcha_solver_api import CaptchaClient, ValidationError from captcha_solver_api.tasks import GeeTestTask, GeeTestTaskProxyless class TestGeeTest: + @pytest.mark.parametrize( + "task", + [ + GeeTestTaskProxyless(websiteURL="https://example.com"), + GeeTestTask( + websiteURL="https://example.com", + proxyType="http", + proxyAddress="1.2.3.4", + proxyPort=8080, + ), + ], + ) + def test_v3_requires_gt_and_challenge(self, task): + with pytest.raises( + ValidationError, + match=r"Missing required GeeTest v3 parameter\(s\): gt, challenge", + ): + task.to_dict() + + @pytest.mark.parametrize("init_parameters", [None, {}, {"captcha_id": " "}]) + def test_v4_requires_captcha_id(self, init_parameters): + task = GeeTestTaskProxyless( + websiteURL="https://example.com", + version=4, + initParameters=init_parameters, + ) + + with pytest.raises( + ValidationError, + match=r"Missing required GeeTest v4 parameter: initParameters\.captcha_id", + ): + task.to_dict() + + def test_rejects_unsupported_version(self): + task = GeeTestTaskProxyless( + websiteURL="https://example.com", + version=5, + ) + + with pytest.raises(ValidationError, match="GeeTest version must be 3 or 4"): + task.to_dict() + def test_v3_to_dict(self): task = GeeTestTaskProxyless( websiteURL="https://example.com",