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.
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/PKG-INFO +58 -7
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/README.md +53 -6
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/__init__.py +8 -8
- captcha_solver_api-1.0.3/captcha_solver_api/_version.py +1 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/async_client.py +10 -5
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/client.py +10 -5
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/exceptions.py +0 -8
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/tasks.py +11 -4
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/PKG-INFO +58 -7
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/requires.txt +4 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/pyproject.toml +35 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/tests/test_polling.py +29 -27
- captcha_solver_api-1.0.2/captcha_solver_api/_version.py +0 -1
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/LICENSE.md +0 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api/py.typed +0 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/SOURCES.txt +0 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/dependency_links.txt +0 -0
- {captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-

|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
46
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
47
|
+
[](https://pepy.tech/project/captcha-solver-api)
|
|
48
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
49
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
50
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
|
|
51
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
|
|
52
|
+
[](https://github.com/astral-sh/ruff)
|
|
53
|
+
[](https://mypy-lang.org/)
|
|
54
|
+
[](https://github.com/captcha-solver-api/python-sdk/releases)
|
|
55
|
+
[](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
|
|
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,
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-

|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
6
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
7
|
+
[](https://pepy.tech/project/captcha-solver-api)
|
|
8
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
9
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
10
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
|
|
11
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
|
|
12
|
+
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
[](https://mypy-lang.org/)
|
|
14
|
+
[](https://github.com/captcha-solver-api/python-sdk/releases)
|
|
15
|
+
[](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
|
|
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,
|
|
324
|
-
|
|
325
|
-
|
|
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 .
|
|
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
|
-
|
|
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
|
-
"
|
|
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) ->
|
|
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
|
-
|
|
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) ->
|
|
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
|
-
|
|
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,
|
|
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
|
|
307
|
-
|
|
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
|
|
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.
|
|
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
|
-

|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
46
|
+
[](https://pypi.org/project/captcha-solver-api/)
|
|
47
|
+
[](https://pepy.tech/project/captcha-solver-api)
|
|
48
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
49
|
+
[](https://github.com/captcha-solver-api/python-sdk/actions/workflows/tests.yml)
|
|
50
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/captcha_solver_api/py.typed)
|
|
51
|
+
[](https://github.com/captcha-solver-api/python-sdk/blob/main/LICENSE.md)
|
|
52
|
+
[](https://github.com/astral-sh/ruff)
|
|
53
|
+
[](https://mypy-lang.org/)
|
|
54
|
+
[](https://github.com/captcha-solver-api/python-sdk/releases)
|
|
55
|
+
[](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
|
|
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,
|
|
360
|
-
|
|
361
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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(
|
|
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,
|
|
37
|
+
monkeypatch.setattr(sync_module, "time", clock)
|
|
38
38
|
|
|
39
39
|
def request(endpoint, payload):
|
|
40
|
-
if endpoint ==
|
|
40
|
+
if endpoint == "createTask":
|
|
41
41
|
assert clock.now == 0
|
|
42
|
-
return {
|
|
43
|
-
assert payload[
|
|
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 {
|
|
47
|
-
return {
|
|
46
|
+
return {"errorId": 0, "status": "ready", "solution": {"token": "done"}}
|
|
47
|
+
return {"errorId": 0, "status": "processing"}
|
|
48
48
|
|
|
49
|
-
options = {} if interval is None else {
|
|
50
|
-
with CaptchaClient(
|
|
51
|
-
monkeypatch.setattr(client,
|
|
52
|
-
task = RecaptchaV2TaskProxyless(
|
|
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) == {
|
|
58
|
+
assert client.solve(task, timeout=timeout) == {"token": "done"}
|
|
59
59
|
assert polls == expected_polls
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
@pytest.mark.parametrize(
|
|
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,
|
|
67
|
-
monkeypatch.setattr(
|
|
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 ==
|
|
72
|
+
if endpoint == "createTask":
|
|
71
73
|
assert clock.now == 0
|
|
72
|
-
return {
|
|
73
|
-
assert payload[
|
|
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 {
|
|
77
|
-
return {
|
|
78
|
+
return {"errorId": 0, "status": "ready", "solution": {"token": "done"}}
|
|
79
|
+
return {"errorId": 0, "status": "processing"}
|
|
78
80
|
|
|
79
|
-
options = {} if interval is None else {
|
|
80
|
-
async with AsyncCaptchaClient(
|
|
81
|
-
monkeypatch.setattr(client,
|
|
82
|
-
task = RecaptchaV2TaskProxyless(
|
|
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) == {
|
|
90
|
+
assert await client.solve(task, timeout=timeout) == {"token": "done"}
|
|
89
91
|
assert polls == expected_polls
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.2"
|
|
File without changes
|
|
File without changes
|
{captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{captcha_solver_api-1.0.2 → captcha_solver_api-1.0.3}/captcha_solver_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|