captcha-solver-api 1.0.2__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/PKG-INFO +58 -7
  2. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/README.md +53 -6
  3. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/__init__.py +8 -8
  4. captcha_solver_api-1.0.3/captcha_solver_api/_version.py +1 -0
  5. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/async_client.py +10 -5
  6. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/client.py +10 -5
  7. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/exceptions.py +0 -8
  8. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/tasks.py +11 -4
  9. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/PKG-INFO +58 -7
  10. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/requires.txt +4 -0
  11. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/pyproject.toml +35 -0
  12. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/tests/test_polling.py +29 -27
  13. captcha_solver_api-1.0.2/captcha_solver_api/_version.py +0 -1
  14. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/LICENSE.md +0 -0
  15. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/py.typed +0 -0
  16. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/SOURCES.txt +0 -0
  17. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/dependency_links.txt +0 -0
  18. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/top_level.txt +0 -0
  19. {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: captcha-solver-api
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: Official Python SDK for solving reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3/v4, Yandex SmartCaptcha, Tencent, and image/click captchas via the Captcha Solver API
5
5
  Author: Captcha Solver Team
6
6
  License-Expression: MIT
@@ -32,11 +32,27 @@ Requires-Dist: pytest>=7; extra == "dev"
32
32
  Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
33
33
  Requires-Dist: pytest-mock>=3; extra == "dev"
34
34
  Requires-Dist: python-dotenv>=1.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.6; extra == "dev"
36
+ Requires-Dist: mypy>=1.10; extra == "dev"
37
+ Requires-Dist: types-requests>=2.28; extra == "dev"
38
+ Requires-Dist: pytest-cov>=4; extra == "dev"
35
39
  Dynamic: license-file
36
40
 
37
41
  # Captcha Solver Python SDK
38
42
 
39
- ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png)
43
+ ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png?v=09a4ebb)
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/captcha-solver-api?logo=pypi&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/captcha-solver-api?logo=python&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
47
+ [![Downloads](https://img.shields.io/pepy/dt/captcha-solver-api)](https://pepy.tech/project/captcha-solver-api)
48
+ [![Tests](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
49
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/dzmitry-duboyski/77b084ed0d46c740183f89ec66af8a95/raw/python-sdk-coverage.json)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
50
+ [![Typed](https://img.shields.io/badge/typing-typed-blue)](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
51
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
52
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
53
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
54
+ [![GitHub Release](https://img.shields.io/github/v/release/captcha-solver-api/python-sdk)](https://github.com/captcha-solver-api/python-sdk/releases)
55
+ [![JavaScript SDK](https://img.shields.io/badge/also_available-JavaScript_SDK-f7df1e?logo=javascript&logoColor=black)](https://github.com/captcha-solver-api/javascript-sdk)
40
56
 
41
57
  Official Python SDK for the Captcha Solver API. Solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, Yandex SmartCaptcha, Tencent, and image/click captchas with a single method call -- sync or async.
42
58
 
@@ -47,6 +63,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
47
63
  - [Installation](#installation)
48
64
  - [Configuration](#configuration)
49
65
  - [Quick Start](#quick-start)
66
+ - [Build Faster with AI](#build-faster-with-ai)
50
67
  - [Supported CAPTCHA Types](#supported-captcha-types)
51
68
  - [Client Reference](#client-reference)
52
69
  - [CaptchaClient(...)](#captchaclient)
@@ -74,6 +91,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
74
91
  - [Running the examples](#running-the-examples)
75
92
  - [Requirements](#requirements)
76
93
  - [API Documentation](#api-documentation)
94
+ - [Useful Links](#useful-links)
77
95
  - [License](#license)
78
96
 
79
97
  ## Installation
@@ -124,6 +142,23 @@ result = client.solve(task)
124
142
  print(result["gRecaptchaResponse"])
125
143
  ```
126
144
 
145
+ ## Build Faster with AI
146
+
147
+ Use our [`llms.txt`](https://captcha-solver.com/llms.txt) as context when asking an
148
+ AI assistant to build or update your integration. It contains the current API
149
+ format, supported task parameters, SDK installation instructions, solution
150
+ fields, and error codes in one file.
151
+
152
+ Copy this prompt and add a description of the page and CAPTCHA you need to
153
+ handle:
154
+
155
+ ```text
156
+ Read https://captcha-solver.com/llms.txt and use the Captcha Solver Python SDK.
157
+ Write a complete integration for this task: [describe your task here].
158
+ Use the documented task parameters and show how to apply the returned solution
159
+ on the target page.
160
+ ```
161
+
127
162
  ## Supported CAPTCHA Types
128
163
 
129
164
  | Type | Proxyless | With Proxy |
@@ -353,12 +388,13 @@ that the target page expects in `cf-turnstile-response`.
353
388
  | `action` | no | Value of the widget's `data-action` attribute, if set. |
354
389
  | `data` | no | Custom payload from the widget's `data-cdata` attribute, if set. |
355
390
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
356
- | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
391
+ | `userAgent` | no | Current browser User-Agent. Pass it for Cloudflare Challenge pages; it is not needed for a standalone widget. |
357
392
 
358
393
  **Response:** `token` -- submit as `cf-turnstile-response`. For Cloudflare
359
- Challenge pages, the solution also includes `userAgent`; use that User-Agent
360
- when submitting the token. Pass `action`, `data`, and `pagedata` when the page
361
- provides them. The API field is spelled `pagedata`, all lowercase.
394
+ Challenge pages, pass `action`, `data`, `pagedata`, and the current browser
395
+ `userAgent` in the task. The solution also contains `userAgent`; switch the
396
+ browser or HTTP client to this returned value before invoking the callback with
397
+ the token. The API field is spelled `pagedata`, all lowercase.
362
398
 
363
399
  ```python
364
400
  from captcha_solver_api import CaptchaClient
@@ -366,10 +402,15 @@ from captcha_solver_api.tasks import TurnstileTaskProxyless
366
402
  client = CaptchaClient("your_api_key")
367
403
  task = TurnstileTaskProxyless(
368
404
  websiteURL="https://example.com/login",
369
- websiteKey="YOUR_WEBSITE_KEY"
405
+ websiteKey="YOUR_WEBSITE_KEY",
406
+ action="managed",
407
+ data="INTERCEPTED_CDATA",
408
+ pagedata="INTERCEPTED_CHL_PAGE_DATA",
409
+ userAgent="CURRENT_BROWSER_USER_AGENT",
370
410
  )
371
411
  result = client.solve(task)
372
412
  print(result["token"])
413
+ print(result.get("userAgent"))
373
414
  ```
374
415
 
375
416
  With proxy, use `TurnstileTask` (same proxy fields as reCAPTCHA v2).
@@ -733,6 +774,16 @@ page and session parameters.
733
774
 
734
775
  Full API reference: https://captcha-solver.com/en/docs/captcha-types
735
776
 
777
+ ## Useful Links
778
+
779
+ - [JavaScript SDK](https://github.com/captcha-solver-api/javascript-sdk)
780
+ - [Python examples](https://github.com/captcha-solver-api/python-examples)
781
+ - [Selenium Python examples](https://github.com/captcha-solver-api/captcha-solver-selenium-python-examples)
782
+ - [JavaScript examples](https://github.com/captcha-solver-api/javascript-examples)
783
+ - [Tencent CAPTCHA automation examples](https://github.com/captcha-solver-api/How-to-Automate-Tencent-CAPTCHA)
784
+ - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) — a working browser automation example for Cloudflare Turnstile.
785
+ - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) — a step-by-step guide with Python and JavaScript SDK examples.
786
+
736
787
  ## License
737
788
 
738
789
  This project is licensed under the MIT License. See [LICENSE.md](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md) for details.
@@ -1,6 +1,18 @@
1
1
  # Captcha Solver Python SDK
2
2
 
3
- ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png)
3
+ ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png?v=09a4ebb)
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/captcha-solver-api?logo=pypi&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/captcha-solver-api?logo=python&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
7
+ [![Downloads](https://img.shields.io/pepy/dt/captcha-solver-api)](https://pepy.tech/project/captcha-solver-api)
8
+ [![Tests](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
9
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/dzmitry-duboyski/77b084ed0d46c740183f89ec66af8a95/raw/python-sdk-coverage.json)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
10
+ [![Typed](https://img.shields.io/badge/typing-typed-blue)](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
12
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
13
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
14
+ [![GitHub Release](https://img.shields.io/github/v/release/captcha-solver-api/python-sdk)](https://github.com/captcha-solver-api/python-sdk/releases)
15
+ [![JavaScript SDK](https://img.shields.io/badge/also_available-JavaScript_SDK-f7df1e?logo=javascript&logoColor=black)](https://github.com/captcha-solver-api/javascript-sdk)
4
16
 
5
17
  Official Python SDK for the Captcha Solver API. Solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, Yandex SmartCaptcha, Tencent, and image/click captchas with a single method call -- sync or async.
6
18
 
@@ -11,6 +23,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
11
23
  - [Installation](#installation)
12
24
  - [Configuration](#configuration)
13
25
  - [Quick Start](#quick-start)
26
+ - [Build Faster with AI](#build-faster-with-ai)
14
27
  - [Supported CAPTCHA Types](#supported-captcha-types)
15
28
  - [Client Reference](#client-reference)
16
29
  - [CaptchaClient(...)](#captchaclient)
@@ -38,6 +51,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
38
51
  - [Running the examples](#running-the-examples)
39
52
  - [Requirements](#requirements)
40
53
  - [API Documentation](#api-documentation)
54
+ - [Useful Links](#useful-links)
41
55
  - [License](#license)
42
56
 
43
57
  ## Installation
@@ -88,6 +102,23 @@ result = client.solve(task)
88
102
  print(result["gRecaptchaResponse"])
89
103
  ```
90
104
 
105
+ ## Build Faster with AI
106
+
107
+ Use our [`llms.txt`](https://captcha-solver.com/llms.txt) as context when asking an
108
+ AI assistant to build or update your integration. It contains the current API
109
+ format, supported task parameters, SDK installation instructions, solution
110
+ fields, and error codes in one file.
111
+
112
+ Copy this prompt and add a description of the page and CAPTCHA you need to
113
+ handle:
114
+
115
+ ```text
116
+ Read https://captcha-solver.com/llms.txt and use the Captcha Solver Python SDK.
117
+ Write a complete integration for this task: [describe your task here].
118
+ Use the documented task parameters and show how to apply the returned solution
119
+ on the target page.
120
+ ```
121
+
91
122
  ## Supported CAPTCHA Types
92
123
 
93
124
  | Type | Proxyless | With Proxy |
@@ -317,12 +348,13 @@ that the target page expects in `cf-turnstile-response`.
317
348
  | `action` | no | Value of the widget's `data-action` attribute, if set. |
318
349
  | `data` | no | Custom payload from the widget's `data-cdata` attribute, if set. |
319
350
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
320
- | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
351
+ | `userAgent` | no | Current browser User-Agent. Pass it for Cloudflare Challenge pages; it is not needed for a standalone widget. |
321
352
 
322
353
  **Response:** `token` -- submit as `cf-turnstile-response`. For Cloudflare
323
- Challenge pages, the solution also includes `userAgent`; use that User-Agent
324
- when submitting the token. Pass `action`, `data`, and `pagedata` when the page
325
- provides them. The API field is spelled `pagedata`, all lowercase.
354
+ Challenge pages, pass `action`, `data`, `pagedata`, and the current browser
355
+ `userAgent` in the task. The solution also contains `userAgent`; switch the
356
+ browser or HTTP client to this returned value before invoking the callback with
357
+ the token. The API field is spelled `pagedata`, all lowercase.
326
358
 
327
359
  ```python
328
360
  from captcha_solver_api import CaptchaClient
@@ -330,10 +362,15 @@ from captcha_solver_api.tasks import TurnstileTaskProxyless
330
362
  client = CaptchaClient("your_api_key")
331
363
  task = TurnstileTaskProxyless(
332
364
  websiteURL="https://example.com/login",
333
- websiteKey="YOUR_WEBSITE_KEY"
365
+ websiteKey="YOUR_WEBSITE_KEY",
366
+ action="managed",
367
+ data="INTERCEPTED_CDATA",
368
+ pagedata="INTERCEPTED_CHL_PAGE_DATA",
369
+ userAgent="CURRENT_BROWSER_USER_AGENT",
334
370
  )
335
371
  result = client.solve(task)
336
372
  print(result["token"])
373
+ print(result.get("userAgent"))
337
374
  ```
338
375
 
339
376
  With proxy, use `TurnstileTask` (same proxy fields as reCAPTCHA v2).
@@ -697,6 +734,16 @@ page and session parameters.
697
734
 
698
735
  Full API reference: https://captcha-solver.com/en/docs/captcha-types
699
736
 
737
+ ## Useful Links
738
+
739
+ - [JavaScript SDK](https://github.com/captcha-solver-api/javascript-sdk)
740
+ - [Python examples](https://github.com/captcha-solver-api/python-examples)
741
+ - [Selenium Python examples](https://github.com/captcha-solver-api/captcha-solver-selenium-python-examples)
742
+ - [JavaScript examples](https://github.com/captcha-solver-api/javascript-examples)
743
+ - [Tencent CAPTCHA automation examples](https://github.com/captcha-solver-api/How-to-Automate-Tencent-CAPTCHA)
744
+ - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) — a working browser automation example for Cloudflare Turnstile.
745
+ - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) — a step-by-step guide with Python and JavaScript SDK examples.
746
+
700
747
  ## License
701
748
 
702
749
  This project is licensed under the MIT License. See [LICENSE.md](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md) for details.
@@ -13,26 +13,26 @@ Example:
13
13
  result = client.solve(task)
14
14
  """
15
15
 
16
- from .client import CaptchaClient
16
+ from ._version import __version__
17
17
  from .async_client import AsyncCaptchaClient
18
+ from .client import CaptchaClient
18
19
  from .exceptions import (
19
- CaptchaError,
20
20
  ApiError,
21
- NetworkError,
21
+ CaptchaError,
22
22
  CaptchaTimeoutError,
23
+ NetworkError,
23
24
  TimeoutError,
24
25
  ValidationError,
25
26
  )
26
27
 
27
- from ._version import __version__
28
-
29
28
  __all__ = [
30
- "CaptchaClient",
29
+ "ApiError",
31
30
  "AsyncCaptchaClient",
31
+ "CaptchaClient",
32
32
  "CaptchaError",
33
- "ApiError",
34
- "NetworkError",
35
33
  "CaptchaTimeoutError",
34
+ "NetworkError",
36
35
  "TimeoutError",
37
36
  "ValidationError",
37
+ "__version__",
38
38
  ]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.3"
@@ -15,8 +15,8 @@ import httpx
15
15
  from ._version import __version__
16
16
  from .exceptions import (
17
17
  ApiError,
18
- NetworkError,
19
18
  CaptchaTimeoutError,
19
+ NetworkError,
20
20
  ValidationError,
21
21
  )
22
22
 
@@ -87,7 +87,7 @@ class AsyncCaptchaClient:
87
87
  (`async with AsyncCaptchaClient(...) as c:`) to have it closed automatically."""
88
88
  await self._client.aclose()
89
89
 
90
- async def __aenter__(self) -> "AsyncCaptchaClient":
90
+ async def __aenter__(self) -> AsyncCaptchaClient:
91
91
  return self
92
92
 
93
93
  async def __aexit__(self, *exc_info: Any) -> None:
@@ -113,6 +113,8 @@ class AsyncCaptchaClient:
113
113
  except ValueError as exc:
114
114
  raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc
115
115
 
116
+ if not isinstance(data, dict):
117
+ raise NetworkError(f"Unexpected API response shape: {type(data).__name__}")
116
118
  return data
117
119
 
118
120
  def _ensure_success(self, data: Dict[str, Any]) -> None:
@@ -154,7 +156,7 @@ class AsyncCaptchaClient:
154
156
 
155
157
  data = await self._request("createTask", payload)
156
158
  self._ensure_success(data)
157
- return data["taskId"]
159
+ return int(data["taskId"])
158
160
 
159
161
  async def get_task_result(self, task_id: int) -> Dict[str, Any]:
160
162
  """Fetch the current status of a previously submitted task.
@@ -203,7 +205,7 @@ class AsyncCaptchaClient:
203
205
  payload = {"clientKey": self.client_key}
204
206
  data = await self._request("getBalance", payload)
205
207
  self._ensure_success(data)
206
- return data["balance"]
208
+ return float(data["balance"])
207
209
 
208
210
  async def solve(
209
211
  self,
@@ -248,6 +250,9 @@ class AsyncCaptchaClient:
248
250
  result = await self.get_task_result(task_id)
249
251
 
250
252
  if result.get("status") == "ready":
251
- return result["solution"]
253
+ solution = result["solution"]
254
+ if not isinstance(solution, dict):
255
+ raise NetworkError("API returned a ready task without a solution object")
256
+ return solution
252
257
 
253
258
  raise CaptchaTimeoutError("Task solving timed out.")
@@ -12,8 +12,8 @@ import requests
12
12
  from ._version import __version__
13
13
  from .exceptions import (
14
14
  ApiError,
15
- NetworkError,
16
15
  CaptchaTimeoutError,
16
+ NetworkError,
17
17
  ValidationError,
18
18
  )
19
19
 
@@ -76,7 +76,7 @@ class CaptchaClient:
76
76
  to have it closed automatically."""
77
77
  self.session.close()
78
78
 
79
- def __enter__(self) -> "CaptchaClient":
79
+ def __enter__(self) -> CaptchaClient:
80
80
  return self
81
81
 
82
82
  def __exit__(self, *exc_info: Any) -> None:
@@ -102,6 +102,8 @@ class CaptchaClient:
102
102
  except ValueError as exc:
103
103
  raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc
104
104
 
105
+ if not isinstance(data, dict):
106
+ raise NetworkError(f"Unexpected API response shape: {type(data).__name__}")
105
107
  return data
106
108
 
107
109
  def _ensure_success(self, data: Dict[str, Any]) -> None:
@@ -141,7 +143,7 @@ class CaptchaClient:
141
143
 
142
144
  data = self._request("createTask", payload)
143
145
  self._ensure_success(data)
144
- return data["taskId"]
146
+ return int(data["taskId"])
145
147
 
146
148
  def get_task_result(self, task_id: int) -> Dict[str, Any]:
147
149
  """Fetches the current status of a task created with `create_task()`.
@@ -187,7 +189,7 @@ class CaptchaClient:
187
189
  payload = {"clientKey": self.client_key}
188
190
  data = self._request("getBalance", payload)
189
191
  self._ensure_success(data)
190
- return data["balance"]
192
+ return float(data["balance"])
191
193
 
192
194
  def solve(
193
195
  self,
@@ -230,6 +232,9 @@ class CaptchaClient:
230
232
  result = self.get_task_result(task_id)
231
233
 
232
234
  if result.get("status") == "ready":
233
- return result["solution"]
235
+ solution = result["solution"]
236
+ if not isinstance(solution, dict):
237
+ raise NetworkError("API returned a ready task without a solution object")
238
+ return solution
234
239
 
235
240
  raise CaptchaTimeoutError("Task solving timed out.")
@@ -6,20 +6,14 @@ Custom exceptions used by the Captcha Solver SDK.
6
6
  class CaptchaError(Exception):
7
7
  """Base exception for all SDK errors."""
8
8
 
9
- pass
10
-
11
9
 
12
10
  class NetworkError(CaptchaError):
13
11
  """Raised when a network request fails."""
14
12
 
15
- pass
16
-
17
13
 
18
14
  class CaptchaTimeoutError(CaptchaError):
19
15
  """Raised when the operation exceeds the configured timeout."""
20
16
 
21
- pass
22
-
23
17
 
24
18
  # Deprecated alias kept for backward compatibility. It shadows the built-in
25
19
  # ``TimeoutError`` when imported by name, so prefer ``CaptchaTimeoutError``.
@@ -37,5 +31,3 @@ class ApiError(CaptchaError):
37
31
 
38
32
  class ValidationError(CaptchaError):
39
33
  """Raised for client-side argument problems caught before any request is sent."""
40
-
41
- pass
@@ -12,7 +12,7 @@ once the task is `"ready"`.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- from typing import Any, Dict, List, Optional
15
+ from typing import Any, Dict, Optional
16
16
 
17
17
 
18
18
  class BaseTask:
@@ -303,11 +303,14 @@ class TurnstileTaskProxyless(BaseTask):
303
303
  challenge pages beyond the basic widget. Named lowercase (not `pageData`)
304
304
  to match the API field name exactly -- Cloudflare-specific fields are
305
305
  the one place this API doesn't camelCase.
306
- userAgent: User-Agent to solve with. The returned token is tied to it --
307
- submit it with the same User-Agent.
306
+ userAgent: User-Agent of the browser that loaded the challenge. Pass it
307
+ together with `action`, `data`, and `pagedata` for Cloudflare
308
+ Challenge pages. It is not needed for a standalone widget.
308
309
 
309
310
  Returns (`solution` from `solve()`):
310
311
  `token` -- the value to submit as `cf-turnstile-response`.
312
+ `userAgent` -- for Cloudflare Challenge pages, switch the browser or HTTP
313
+ client to this returned User-Agent before invoking the callback.
311
314
  """
312
315
 
313
316
  type = "TurnstileTaskProxyless"
@@ -345,10 +348,14 @@ class TurnstileTask(BaseTask, ProxyMixin):
345
348
  pagedata: Value of the `chlPageData` parameter, needed for some Cloudflare
346
349
  challenge pages beyond the basic widget. Named lowercase (not `pageData`)
347
350
  to match the API field name exactly.
348
- userAgent: User-Agent to solve with. The returned token is tied to it.
351
+ userAgent: User-Agent of the browser that loaded the challenge. Pass it
352
+ for Cloudflare Challenge pages; it is not needed for a standalone
353
+ widget.
349
354
 
350
355
  Returns (`solution` from `solve()`):
351
356
  `token` -- the value to submit as `cf-turnstile-response`.
357
+ `userAgent` -- for Cloudflare Challenge pages, switch the browser or HTTP
358
+ client to this returned User-Agent before invoking the callback.
352
359
  """
353
360
 
354
361
  type = "TurnstileTask"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: captcha-solver-api
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: Official Python SDK for solving reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3/v4, Yandex SmartCaptcha, Tencent, and image/click captchas via the Captcha Solver API
5
5
  Author: Captcha Solver Team
6
6
  License-Expression: MIT
@@ -32,11 +32,27 @@ Requires-Dist: pytest>=7; extra == "dev"
32
32
  Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
33
33
  Requires-Dist: pytest-mock>=3; extra == "dev"
34
34
  Requires-Dist: python-dotenv>=1.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.6; extra == "dev"
36
+ Requires-Dist: mypy>=1.10; extra == "dev"
37
+ Requires-Dist: types-requests>=2.28; extra == "dev"
38
+ Requires-Dist: pytest-cov>=4; extra == "dev"
35
39
  Dynamic: license-file
36
40
 
37
41
  # Captcha Solver Python SDK
38
42
 
39
- ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png)
43
+ ![python-sdk-banner](https://raw.githubusercontent.com/captcha-solver-api/python-sdk/main/assets/repo-banner-python.png?v=09a4ebb)
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/captcha-solver-api?logo=pypi&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/captcha-solver-api?logo=python&logoColor=white)](https://pypi.org/project/captcha-solver-api/)
47
+ [![Downloads](https://img.shields.io/pepy/dt/captcha-solver-api)](https://pepy.tech/project/captcha-solver-api)
48
+ [![Tests](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
49
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/dzmitry-duboyski/77b084ed0d46c740183f89ec66af8a95/raw/python-sdk-coverage.json)](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
50
+ [![Typed](https://img.shields.io/badge/typing-typed-blue)](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
51
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
52
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
53
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
54
+ [![GitHub Release](https://img.shields.io/github/v/release/captcha-solver-api/python-sdk)](https://github.com/captcha-solver-api/python-sdk/releases)
55
+ [![JavaScript SDK](https://img.shields.io/badge/also_available-JavaScript_SDK-f7df1e?logo=javascript&logoColor=black)](https://github.com/captcha-solver-api/javascript-sdk)
40
56
 
41
57
  Official Python SDK for the Captcha Solver API. Solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, Yandex SmartCaptcha, Tencent, and image/click captchas with a single method call -- sync or async.
42
58
 
@@ -47,6 +63,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
47
63
  - [Installation](#installation)
48
64
  - [Configuration](#configuration)
49
65
  - [Quick Start](#quick-start)
66
+ - [Build Faster with AI](#build-faster-with-ai)
50
67
  - [Supported CAPTCHA Types](#supported-captcha-types)
51
68
  - [Client Reference](#client-reference)
52
69
  - [CaptchaClient(...)](#captchaclient)
@@ -74,6 +91,7 @@ Full API reference (all endpoints, error codes, captcha-type details): **https:/
74
91
  - [Running the examples](#running-the-examples)
75
92
  - [Requirements](#requirements)
76
93
  - [API Documentation](#api-documentation)
94
+ - [Useful Links](#useful-links)
77
95
  - [License](#license)
78
96
 
79
97
  ## Installation
@@ -124,6 +142,23 @@ result = client.solve(task)
124
142
  print(result["gRecaptchaResponse"])
125
143
  ```
126
144
 
145
+ ## Build Faster with AI
146
+
147
+ Use our [`llms.txt`](https://captcha-solver.com/llms.txt) as context when asking an
148
+ AI assistant to build or update your integration. It contains the current API
149
+ format, supported task parameters, SDK installation instructions, solution
150
+ fields, and error codes in one file.
151
+
152
+ Copy this prompt and add a description of the page and CAPTCHA you need to
153
+ handle:
154
+
155
+ ```text
156
+ Read https://captcha-solver.com/llms.txt and use the Captcha Solver Python SDK.
157
+ Write a complete integration for this task: [describe your task here].
158
+ Use the documented task parameters and show how to apply the returned solution
159
+ on the target page.
160
+ ```
161
+
127
162
  ## Supported CAPTCHA Types
128
163
 
129
164
  | Type | Proxyless | With Proxy |
@@ -353,12 +388,13 @@ that the target page expects in `cf-turnstile-response`.
353
388
  | `action` | no | Value of the widget's `data-action` attribute, if set. |
354
389
  | `data` | no | Custom payload from the widget's `data-cdata` attribute, if set. |
355
390
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
356
- | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
391
+ | `userAgent` | no | Current browser User-Agent. Pass it for Cloudflare Challenge pages; it is not needed for a standalone widget. |
357
392
 
358
393
  **Response:** `token` -- submit as `cf-turnstile-response`. For Cloudflare
359
- Challenge pages, the solution also includes `userAgent`; use that User-Agent
360
- when submitting the token. Pass `action`, `data`, and `pagedata` when the page
361
- provides them. The API field is spelled `pagedata`, all lowercase.
394
+ Challenge pages, pass `action`, `data`, `pagedata`, and the current browser
395
+ `userAgent` in the task. The solution also contains `userAgent`; switch the
396
+ browser or HTTP client to this returned value before invoking the callback with
397
+ the token. The API field is spelled `pagedata`, all lowercase.
362
398
 
363
399
  ```python
364
400
  from captcha_solver_api import CaptchaClient
@@ -366,10 +402,15 @@ from captcha_solver_api.tasks import TurnstileTaskProxyless
366
402
  client = CaptchaClient("your_api_key")
367
403
  task = TurnstileTaskProxyless(
368
404
  websiteURL="https://example.com/login",
369
- websiteKey="YOUR_WEBSITE_KEY"
405
+ websiteKey="YOUR_WEBSITE_KEY",
406
+ action="managed",
407
+ data="INTERCEPTED_CDATA",
408
+ pagedata="INTERCEPTED_CHL_PAGE_DATA",
409
+ userAgent="CURRENT_BROWSER_USER_AGENT",
370
410
  )
371
411
  result = client.solve(task)
372
412
  print(result["token"])
413
+ print(result.get("userAgent"))
373
414
  ```
374
415
 
375
416
  With proxy, use `TurnstileTask` (same proxy fields as reCAPTCHA v2).
@@ -733,6 +774,16 @@ page and session parameters.
733
774
 
734
775
  Full API reference: https://captcha-solver.com/en/docs/captcha-types
735
776
 
777
+ ## Useful Links
778
+
779
+ - [JavaScript SDK](https://github.com/captcha-solver-api/javascript-sdk)
780
+ - [Python examples](https://github.com/captcha-solver-api/python-examples)
781
+ - [Selenium Python examples](https://github.com/captcha-solver-api/captcha-solver-selenium-python-examples)
782
+ - [JavaScript examples](https://github.com/captcha-solver-api/javascript-examples)
783
+ - [Tencent CAPTCHA automation examples](https://github.com/captcha-solver-api/How-to-Automate-Tencent-CAPTCHA)
784
+ - [Cloudflare Turnstile Puppeteer Demo](https://github.com/captcha-solver-api/cloudflare-turnstile-puppeteer-demo) — a working browser automation example for Cloudflare Turnstile.
785
+ - [How to Automate Tencent CAPTCHA](https://captcha-solver.com/en/blog/how-to-automate-tencent-captcha) — a step-by-step guide with Python and JavaScript SDK examples.
786
+
736
787
  ## License
737
788
 
738
789
  This project is licensed under the MIT License. See [LICENSE.md](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md) for details.
@@ -6,3 +6,7 @@ pytest>=7
6
6
  pytest-asyncio>=0.21
7
7
  pytest-mock>=3
8
8
  python-dotenv>=1.0
9
+ ruff>=0.6
10
+ mypy>=1.10
11
+ types-requests>=2.28
12
+ pytest-cov>=4
@@ -45,6 +45,10 @@ dev = [
45
45
  "pytest-asyncio>=0.21",
46
46
  "pytest-mock>=3",
47
47
  "python-dotenv>=1.0",
48
+ "ruff>=0.6",
49
+ "mypy>=1.10",
50
+ "types-requests>=2.28",
51
+ "pytest-cov>=4",
48
52
  ]
49
53
 
50
54
  [project.urls]
@@ -63,3 +67,34 @@ captcha_solver_api = ["py.typed"]
63
67
 
64
68
  [tool.pytest.ini_options]
65
69
  asyncio_mode = "auto"
70
+
71
+ [tool.coverage.run]
72
+ source = ["captcha_solver_api"]
73
+ branch = true
74
+
75
+ [tool.coverage.report]
76
+ show_missing = true
77
+
78
+ [tool.mypy]
79
+ # mypy 2.x cannot target 3.9; 3.10 is the lowest it accepts. Runtime
80
+ # compatibility with 3.9 is still covered by the CI test matrix.
81
+ python_version = "3.10"
82
+ strict = true
83
+ files = ["captcha_solver_api"]
84
+
85
+ [tool.ruff]
86
+ target-version = "py39"
87
+ line-length = 100
88
+ extend-exclude = [".pytest_cache", "*.md"]
89
+
90
+ [tool.ruff.lint]
91
+ select = ["E", "F", "W", "I", "UP", "B", "N", "PIE", "RUF"]
92
+ ignore = [
93
+ "UP006", "UP035", "UP045", # keep typing.Optional/Dict/List for 3.9 readability
94
+ "N803", "N815", # task fields mirror API camelCase on purpose
95
+ "E501", # long lines in docstrings/tables
96
+ ]
97
+
98
+ [tool.ruff.lint.per-file-ignores]
99
+ "examples/**" = ["E402"] # imports after sys.path/dotenv setup
100
+ "tests/**" = ["RUF012"]
@@ -5,9 +5,9 @@ from unittest.mock import AsyncMock
5
5
 
6
6
  import pytest
7
7
 
8
- import captcha_solver_api.client as sync_module
9
8
  import captcha_solver_api.async_client as async_module
10
- from captcha_solver_api import CaptchaClient, AsyncCaptchaClient, CaptchaTimeoutError
9
+ import captcha_solver_api.client as sync_module
10
+ from captcha_solver_api import AsyncCaptchaClient, CaptchaClient, CaptchaTimeoutError
11
11
  from captcha_solver_api.tasks import RecaptchaV2TaskProxyless
12
12
 
13
13
 
@@ -30,60 +30,62 @@ CASES = [
30
30
  ]
31
31
 
32
32
 
33
- @pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES)
33
+ @pytest.mark.parametrize("interval,timeout,expected_polls,times_out", CASES)
34
34
  def test_sync_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out):
35
35
  clock = Clock()
36
36
  polls = []
37
- monkeypatch.setattr(sync_module, 'time', clock)
37
+ monkeypatch.setattr(sync_module, "time", clock)
38
38
 
39
39
  def request(endpoint, payload):
40
- if endpoint == 'createTask':
40
+ if endpoint == "createTask":
41
41
  assert clock.now == 0
42
- return {'errorId': 0, 'taskId': 100}
43
- assert payload['taskId'] == 100
42
+ return {"errorId": 0, "taskId": 100}
43
+ assert payload["taskId"] == 100
44
44
  polls.append(clock.now)
45
45
  if len(polls) == 2:
46
- return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}}
47
- return {'errorId': 0, 'status': 'processing'}
46
+ return {"errorId": 0, "status": "ready", "solution": {"token": "done"}}
47
+ return {"errorId": 0, "status": "processing"}
48
48
 
49
- options = {} if interval is None else {'polling_interval': interval}
50
- with CaptchaClient('test-key', **options) as client:
51
- monkeypatch.setattr(client, '_request', request)
52
- task = RecaptchaV2TaskProxyless('https://example.com', 'site-key')
49
+ options = {} if interval is None else {"polling_interval": interval}
50
+ with CaptchaClient("test-key", **options) as client:
51
+ monkeypatch.setattr(client, "_request", request)
52
+ task = RecaptchaV2TaskProxyless("https://example.com", "site-key")
53
53
  if times_out:
54
54
  with pytest.raises(CaptchaTimeoutError):
55
55
  client.solve(task, timeout=timeout)
56
56
  assert clock.now == timeout
57
57
  else:
58
- assert client.solve(task, timeout=timeout) == {'token': 'done'}
58
+ assert client.solve(task, timeout=timeout) == {"token": "done"}
59
59
  assert polls == expected_polls
60
60
 
61
61
 
62
- @pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES)
62
+ @pytest.mark.parametrize("interval,timeout,expected_polls,times_out", CASES)
63
63
  async def test_async_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out):
64
64
  clock = Clock()
65
65
  polls = []
66
- monkeypatch.setattr(async_module, 'time', clock)
67
- monkeypatch.setattr(async_module, 'asyncio', SimpleNamespace(sleep=AsyncMock(side_effect=clock.sleep)))
66
+ monkeypatch.setattr(async_module, "time", clock)
67
+ monkeypatch.setattr(
68
+ async_module, "asyncio", SimpleNamespace(sleep=AsyncMock(side_effect=clock.sleep))
69
+ )
68
70
 
69
71
  async def request(endpoint, payload):
70
- if endpoint == 'createTask':
72
+ if endpoint == "createTask":
71
73
  assert clock.now == 0
72
- return {'errorId': 0, 'taskId': 100}
73
- assert payload['taskId'] == 100
74
+ return {"errorId": 0, "taskId": 100}
75
+ assert payload["taskId"] == 100
74
76
  polls.append(clock.now)
75
77
  if len(polls) == 2:
76
- return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}}
77
- return {'errorId': 0, 'status': 'processing'}
78
+ return {"errorId": 0, "status": "ready", "solution": {"token": "done"}}
79
+ return {"errorId": 0, "status": "processing"}
78
80
 
79
- options = {} if interval is None else {'polling_interval': interval}
80
- async with AsyncCaptchaClient('test-key', **options) as client:
81
- monkeypatch.setattr(client, '_request', request)
82
- task = RecaptchaV2TaskProxyless('https://example.com', 'site-key')
81
+ options = {} if interval is None else {"polling_interval": interval}
82
+ async with AsyncCaptchaClient("test-key", **options) as client:
83
+ monkeypatch.setattr(client, "_request", request)
84
+ task = RecaptchaV2TaskProxyless("https://example.com", "site-key")
83
85
  if times_out:
84
86
  with pytest.raises(CaptchaTimeoutError):
85
87
  await client.solve(task, timeout=timeout)
86
88
  assert clock.now == timeout
87
89
  else:
88
- assert await client.solve(task, timeout=timeout) == {'token': 'done'}
90
+ assert await client.solve(task, timeout=timeout) == {"token": "done"}
89
91
  assert polls == expected_polls
@@ -1 +0,0 @@
1
- __version__ = "1.0.2"