captcha-solver-api 1.0.0__tar.gz → 1.0.1__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.0 → captcha_solver_api-1.0.1}/PKG-INFO +34 -21
  2. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/README.md +33 -20
  3. captcha_solver_api-1.0.1/captcha_solver_api/_version.py +1 -0
  4. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/async_client.py +12 -7
  5. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/client.py +12 -7
  6. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api.egg-info/PKG-INFO +34 -21
  7. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api.egg-info/SOURCES.txt +2 -1
  8. captcha_solver_api-1.0.1/tests/test_polling.py +89 -0
  9. captcha_solver_api-1.0.0/captcha_solver_api/_version.py +0 -1
  10. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/LICENSE.md +0 -0
  11. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/__init__.py +0 -0
  12. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/exceptions.py +0 -0
  13. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/py.typed +0 -0
  14. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api/tasks.py +0 -0
  15. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api.egg-info/dependency_links.txt +0 -0
  16. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api.egg-info/requires.txt +0 -0
  17. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/captcha_solver_api.egg-info/top_level.txt +0 -0
  18. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/pyproject.toml +0 -0
  19. {captcha_solver_api-1.0.0 → captcha_solver_api-1.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: captcha-solver-api
3
- Version: 1.0.0
3
+ Version: 1.0.1
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
@@ -110,7 +110,7 @@ client = CaptchaClient("your_api_key")
110
110
 
111
111
  ## Quick Start
112
112
 
113
- Solve a reCAPTCHA v2 in 4 lines.
113
+ Solve a reCAPTCHA v2 with a task object and one `solve()` call.
114
114
 
115
115
  ```python
116
116
  from captcha_solver_api import CaptchaClient
@@ -156,8 +156,8 @@ Constructor.
156
156
  |---|---|---|---|
157
157
  | `client_key` | `str` | required | Your Captcha Solver API key. Raises `ValidationError` if empty. |
158
158
  | `base_url` | `str` | `https://api.captcha-solver.com` | API base URL. Override only for self-hosted/staging deployments. |
159
- | `timeout` | `int` | `120` | Default max seconds `solve()` waits for a solution before raising `CaptchaTimeoutError`. Overridable per call. |
160
- | `polling_interval` | `int` | `3` | Seconds between `getTaskResult` polls inside `solve()`. |
159
+ | `timeout` | `int` | `120` | Polling window after task creation, in seconds. Overridable per call; each HTTP request has a separate 30-second timeout. |
160
+ | `polling_interval` | `int` | `10` | Seconds before the first `getTaskResult` poll and between subsequent polls, matching the official 2Captcha Python SDK. |
161
161
  | `language_pool` | `Optional[str]` | `None` | Default worker pool (`"en"` or `"ru"`) applied to every call that doesn't pass its own `language_pool`. |
162
162
 
163
163
  Both clients hold a reusable connection pool (`requests.Session` / `httpx.AsyncClient`)
@@ -191,9 +191,9 @@ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
191
191
  ### `create_task(task, language_pool=None)`
192
192
 
193
193
  Submits `task` and returns its numeric task ID without waiting for a solution.
194
- Same parameters as `solve()`. Use this instead of `solve()` only if you need to
194
+ Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
195
195
  manage polling yourself (e.g. checking on many tasks from a different process).
196
- Raises `ApiError`, `NetworkError`.
196
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
197
197
 
198
198
  ### `get_task_result(task_id)`
199
199
 
@@ -201,12 +201,12 @@ Fetches the current status of a task created with `create_task()`. Always
201
201
  returns a dict with a `status` key (`"processing"` or `"ready"`); when
202
202
  `"ready"`, also has a `solution` dict. This is a single poll, not a wait --
203
203
  call it repeatedly (as `solve()` does) until `status` is `"ready"`.
204
- Raises `ApiError`, `NetworkError`.
204
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
205
205
 
206
206
  ### `get_balance()`
207
207
 
208
208
  Returns the account's current balance (`float`) in the account's currency.
209
- Raises `ApiError`, `NetworkError`.
209
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
210
210
 
211
211
  ## Captcha Types
212
212
 
@@ -276,8 +276,10 @@ task = RecaptchaV2Task(
276
276
  Use this method to solve the Enterprise version of reCAPTCHA v2 and obtain a
277
277
  token for a page that uses `grecaptcha.enterprise`.
278
278
 
279
- `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask`. Same fields
280
- as reCAPTCHA v2, plus:
279
+ `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask` accept
280
+ `websiteURL`, `websiteKey`, `isInvisible`, `apiDomain`, `userAgent`, and `cookies`,
281
+ plus `enterprisePayload`. Use `enterprisePayload={"s": "..."}` for the
282
+ Enterprise `s` value; these classes do not accept `recaptchaDataSValue`.
281
283
 
282
284
  | Parameter | Required | Description |
283
285
  |---|---|---|
@@ -307,8 +309,8 @@ With proxy, use `RecaptchaV2EnterpriseTask` (same proxy fields as reCAPTCHA v2).
307
309
  Use this method to obtain a score-based reCAPTCHA v3 token for a specific site,
308
310
  action, and minimum score. This variant does not use a proxy.
309
311
 
310
- `RecaptchaV3TaskProxyless`. No proxy variant exists -- v3 is score-based and
311
- invisible, so there's no widget/session to pin to a proxy IP.
312
+ `RecaptchaV3TaskProxyless`. This API supports v3 through the service's IPs;
313
+ there is no corresponding task type for your own proxy.
312
314
 
313
315
  | Parameter | Required | Description |
314
316
  |---|---|---|
@@ -353,7 +355,10 @@ that the target page expects in `cf-turnstile-response`.
353
355
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
354
356
  | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
355
357
 
356
- **Response:** `token` -- submit as `cf-turnstile-response`.
358
+ **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.
357
362
 
358
363
  ```python
359
364
  from captcha_solver_api import CaptchaClient
@@ -425,7 +430,7 @@ pass the values collected from the target page before creating the task.
425
430
  | `version` | no | `3` (default) or `4`. |
426
431
  | `gt` | v3 only | Public key of the GeeTest widget. |
427
432
  | `challenge` | v3 only | Session-specific challenge value from the page -- must be freshly fetched for every request, it cannot be reused. |
428
- | `initParameters` | v4 only | Extra parameters from the page's `initGeetest` call; for v4 must contain `captcha_id`. |
433
+ | `initParameters` | required for v4; optional for v3 | Extra initialization parameters; for v4 must contain `captcha_id`. |
429
434
  | `geetestApiServerSubdomain` | no | Custom GeeTest API subdomain, if the site uses one. |
430
435
  | `userAgent` | no | User-Agent to solve with. |
431
436
  | `risk_type` | no | Value of the `risk_type` parameter from the captcha-loading request, if present. Dynamic, single-use, and time-limited. |
@@ -598,6 +603,14 @@ it for every other call:
598
603
  result = client.solve(task, timeout=300)
599
604
  ```
600
605
 
606
+ The [API requires at least 5 seconds between polls](https://captcha-solver.com/en/docs/how-it-works).
607
+ The SDK defaults to 10 seconds, like the official 2Captcha Python SDK. `solve()`
608
+ waits for `polling_interval` before its first poll and between polls.
609
+ Its default 120-second polling window is independent of the API's five-minute
610
+ task lifetime. A local timeout does not cancel a task or mean that the API
611
+ failed to solve it. Use `create_task()` and keep its `task_id` if you need to
612
+ check the same task later; calling `solve()` again creates a new task.
613
+
601
614
  ### Worker language pool
602
615
 
603
616
  Set a default `language_pool` once at construction instead of passing it to every call:
@@ -683,9 +696,10 @@ See the dedicated [examples documentation](https://github.com/captcha-solver-api
683
696
  sync/async example list, setup steps, expected results, and placeholder guidance.
684
697
 
685
698
  - **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
686
- `yandex_smartcaptcha_image.py`) run end-to-end with nothing but a valid
699
+ `yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
687
700
  `CAPTCHA_API_KEY` -- they read sample images bundled in
688
701
  [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
702
+ `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
689
703
  - **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
690
704
  `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
691
705
  placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
@@ -709,12 +723,11 @@ python examples/sync/image_to_text.py
709
723
  python examples/sync/coordinates.py
710
724
  ```
711
725
 
712
- **Verified against the live API** during development, using real target pages and
713
- real proxy credentials in place of the placeholders shown above: every captcha
714
- type in this SDK -- proxyless and with proxy -- returned a real, correctly-shaped
715
- solution when pointed at a genuine target. `geetest_v3.py`'s request shape was
716
- likewise confirmed correct when given a real, freshly-fetched `gt`/`challenge`
717
- pair.
726
+ Live checks with real parameters have exercised all supported task types.
727
+ Individual tasks may return `ERROR_CAPTCHA_UNSOLVABLE` or exceed the client's
728
+ polling timeout. A `ready` response confirms that the service returned a
729
+ solution; the target website must still accept the token with the matching
730
+ page and session parameters.
718
731
 
719
732
  ## Requirements
720
733
 
@@ -74,7 +74,7 @@ client = CaptchaClient("your_api_key")
74
74
 
75
75
  ## Quick Start
76
76
 
77
- Solve a reCAPTCHA v2 in 4 lines.
77
+ Solve a reCAPTCHA v2 with a task object and one `solve()` call.
78
78
 
79
79
  ```python
80
80
  from captcha_solver_api import CaptchaClient
@@ -120,8 +120,8 @@ Constructor.
120
120
  |---|---|---|---|
121
121
  | `client_key` | `str` | required | Your Captcha Solver API key. Raises `ValidationError` if empty. |
122
122
  | `base_url` | `str` | `https://api.captcha-solver.com` | API base URL. Override only for self-hosted/staging deployments. |
123
- | `timeout` | `int` | `120` | Default max seconds `solve()` waits for a solution before raising `CaptchaTimeoutError`. Overridable per call. |
124
- | `polling_interval` | `int` | `3` | Seconds between `getTaskResult` polls inside `solve()`. |
123
+ | `timeout` | `int` | `120` | Polling window after task creation, in seconds. Overridable per call; each HTTP request has a separate 30-second timeout. |
124
+ | `polling_interval` | `int` | `10` | Seconds before the first `getTaskResult` poll and between subsequent polls, matching the official 2Captcha Python SDK. |
125
125
  | `language_pool` | `Optional[str]` | `None` | Default worker pool (`"en"` or `"ru"`) applied to every call that doesn't pass its own `language_pool`. |
126
126
 
127
127
  Both clients hold a reusable connection pool (`requests.Session` / `httpx.AsyncClient`)
@@ -155,9 +155,9 @@ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
155
155
  ### `create_task(task, language_pool=None)`
156
156
 
157
157
  Submits `task` and returns its numeric task ID without waiting for a solution.
158
- Same parameters as `solve()`. Use this instead of `solve()` only if you need to
158
+ Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
159
159
  manage polling yourself (e.g. checking on many tasks from a different process).
160
- Raises `ApiError`, `NetworkError`.
160
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
161
161
 
162
162
  ### `get_task_result(task_id)`
163
163
 
@@ -165,12 +165,12 @@ Fetches the current status of a task created with `create_task()`. Always
165
165
  returns a dict with a `status` key (`"processing"` or `"ready"`); when
166
166
  `"ready"`, also has a `solution` dict. This is a single poll, not a wait --
167
167
  call it repeatedly (as `solve()` does) until `status` is `"ready"`.
168
- Raises `ApiError`, `NetworkError`.
168
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
169
169
 
170
170
  ### `get_balance()`
171
171
 
172
172
  Returns the account's current balance (`float`) in the account's currency.
173
- Raises `ApiError`, `NetworkError`.
173
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
174
174
 
175
175
  ## Captcha Types
176
176
 
@@ -240,8 +240,10 @@ task = RecaptchaV2Task(
240
240
  Use this method to solve the Enterprise version of reCAPTCHA v2 and obtain a
241
241
  token for a page that uses `grecaptcha.enterprise`.
242
242
 
243
- `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask`. Same fields
244
- as reCAPTCHA v2, plus:
243
+ `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask` accept
244
+ `websiteURL`, `websiteKey`, `isInvisible`, `apiDomain`, `userAgent`, and `cookies`,
245
+ plus `enterprisePayload`. Use `enterprisePayload={"s": "..."}` for the
246
+ Enterprise `s` value; these classes do not accept `recaptchaDataSValue`.
245
247
 
246
248
  | Parameter | Required | Description |
247
249
  |---|---|---|
@@ -271,8 +273,8 @@ With proxy, use `RecaptchaV2EnterpriseTask` (same proxy fields as reCAPTCHA v2).
271
273
  Use this method to obtain a score-based reCAPTCHA v3 token for a specific site,
272
274
  action, and minimum score. This variant does not use a proxy.
273
275
 
274
- `RecaptchaV3TaskProxyless`. No proxy variant exists -- v3 is score-based and
275
- invisible, so there's no widget/session to pin to a proxy IP.
276
+ `RecaptchaV3TaskProxyless`. This API supports v3 through the service's IPs;
277
+ there is no corresponding task type for your own proxy.
276
278
 
277
279
  | Parameter | Required | Description |
278
280
  |---|---|---|
@@ -317,7 +319,10 @@ that the target page expects in `cf-turnstile-response`.
317
319
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
318
320
  | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
319
321
 
320
- **Response:** `token` -- submit as `cf-turnstile-response`.
322
+ **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.
321
326
 
322
327
  ```python
323
328
  from captcha_solver_api import CaptchaClient
@@ -389,7 +394,7 @@ pass the values collected from the target page before creating the task.
389
394
  | `version` | no | `3` (default) or `4`. |
390
395
  | `gt` | v3 only | Public key of the GeeTest widget. |
391
396
  | `challenge` | v3 only | Session-specific challenge value from the page -- must be freshly fetched for every request, it cannot be reused. |
392
- | `initParameters` | v4 only | Extra parameters from the page's `initGeetest` call; for v4 must contain `captcha_id`. |
397
+ | `initParameters` | required for v4; optional for v3 | Extra initialization parameters; for v4 must contain `captcha_id`. |
393
398
  | `geetestApiServerSubdomain` | no | Custom GeeTest API subdomain, if the site uses one. |
394
399
  | `userAgent` | no | User-Agent to solve with. |
395
400
  | `risk_type` | no | Value of the `risk_type` parameter from the captcha-loading request, if present. Dynamic, single-use, and time-limited. |
@@ -562,6 +567,14 @@ it for every other call:
562
567
  result = client.solve(task, timeout=300)
563
568
  ```
564
569
 
570
+ The [API requires at least 5 seconds between polls](https://captcha-solver.com/en/docs/how-it-works).
571
+ The SDK defaults to 10 seconds, like the official 2Captcha Python SDK. `solve()`
572
+ waits for `polling_interval` before its first poll and between polls.
573
+ Its default 120-second polling window is independent of the API's five-minute
574
+ task lifetime. A local timeout does not cancel a task or mean that the API
575
+ failed to solve it. Use `create_task()` and keep its `task_id` if you need to
576
+ check the same task later; calling `solve()` again creates a new task.
577
+
565
578
  ### Worker language pool
566
579
 
567
580
  Set a default `language_pool` once at construction instead of passing it to every call:
@@ -647,9 +660,10 @@ See the dedicated [examples documentation](https://github.com/captcha-solver-api
647
660
  sync/async example list, setup steps, expected results, and placeholder guidance.
648
661
 
649
662
  - **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
650
- `yandex_smartcaptcha_image.py`) run end-to-end with nothing but a valid
663
+ `yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
651
664
  `CAPTCHA_API_KEY` -- they read sample images bundled in
652
665
  [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
666
+ `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
653
667
  - **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
654
668
  `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
655
669
  placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
@@ -673,12 +687,11 @@ python examples/sync/image_to_text.py
673
687
  python examples/sync/coordinates.py
674
688
  ```
675
689
 
676
- **Verified against the live API** during development, using real target pages and
677
- real proxy credentials in place of the placeholders shown above: every captcha
678
- type in this SDK -- proxyless and with proxy -- returned a real, correctly-shaped
679
- solution when pointed at a genuine target. `geetest_v3.py`'s request shape was
680
- likewise confirmed correct when given a real, freshly-fetched `gt`/`challenge`
681
- pair.
690
+ Live checks with real parameters have exercised all supported task types.
691
+ Individual tasks may return `ERROR_CAPTCHA_UNSOLVABLE` or exceed the client's
692
+ polling timeout. A `ready` response confirms that the service returned a
693
+ solution; the target website must still accept the token with the matching
694
+ page and session parameters.
682
695
 
683
696
  ## Requirements
684
697
 
@@ -0,0 +1 @@
1
+ __version__ = "1.0.1"
@@ -42,7 +42,7 @@ class AsyncCaptchaClient:
42
42
  client_key: str,
43
43
  base_url: str = "https://api.captcha-solver.com",
44
44
  timeout: int = 120,
45
- polling_interval: int = 3,
45
+ polling_interval: int = 10,
46
46
  language_pool: Optional[str] = None,
47
47
  ) -> None:
48
48
  """
@@ -52,8 +52,9 @@ class AsyncCaptchaClient:
52
52
  deployments.
53
53
  timeout: Default max seconds `solve()` waits for a solution before
54
54
  raising `CaptchaTimeoutError`. Can be overridden per call.
55
- polling_interval: Seconds to wait between `getTaskResult` polls
56
- inside `solve()`.
55
+ polling_interval: Seconds to wait before the first `getTaskResult`
56
+ poll and between subsequent polls inside `solve()`. Defaults
57
+ to 10 seconds, matching the official 2Captcha Python SDK.
57
58
  language_pool: Default worker pool selector (e.g. `"en"` or `"ru"`)
58
59
  applied to every `create_task()`/`solve()` call that doesn't pass
59
60
  its own `language_pool`. Leave unset to use the account's default
@@ -174,6 +175,7 @@ class AsyncCaptchaClient:
174
175
  Raises:
175
176
  ApiError: The API reports an error for the task.
176
177
  NetworkError: The request failed at the transport level.
178
+ CaptchaTimeoutError: The HTTP request timed out.
177
179
  """
178
180
  payload = {
179
181
  "clientKey": self.client_key,
@@ -196,6 +198,7 @@ class AsyncCaptchaClient:
196
198
  Raises:
197
199
  ApiError: The API key is invalid or the account cannot be resolved.
198
200
  NetworkError: The request failed at the transport level.
201
+ CaptchaTimeoutError: The HTTP request timed out.
199
202
  """
200
203
  payload = {"clientKey": self.client_key}
201
204
  data = await self._request("getBalance", payload)
@@ -235,14 +238,16 @@ class AsyncCaptchaClient:
235
238
 
236
239
  task_id = await self.create_task(task, language_pool=language_pool)
237
240
 
238
- deadline = time.time() + (timeout if timeout is not None else self.timeout)
241
+ deadline = time.monotonic() + (timeout if timeout is not None else self.timeout)
239
242
 
240
- while time.time() < deadline:
243
+ while time.monotonic() < deadline:
244
+ remaining = deadline - time.monotonic()
245
+ await asyncio.sleep(min(self.polling_interval, max(0, remaining)))
246
+ if time.monotonic() >= deadline:
247
+ break
241
248
  result = await self.get_task_result(task_id)
242
249
 
243
250
  if result.get("status") == "ready":
244
251
  return result["solution"]
245
252
 
246
- await asyncio.sleep(self.polling_interval)
247
-
248
253
  raise CaptchaTimeoutError("Task solving timed out.")
@@ -31,7 +31,7 @@ class CaptchaClient:
31
31
  client_key: str,
32
32
  base_url: str = "https://api.captcha-solver.com",
33
33
  timeout: int = 120,
34
- polling_interval: int = 3,
34
+ polling_interval: int = 10,
35
35
  language_pool: Optional[str] = None,
36
36
  ) -> None:
37
37
  """
@@ -41,8 +41,9 @@ class CaptchaClient:
41
41
  deployments.
42
42
  timeout: Default max seconds `solve()` waits for a solution before
43
43
  raising `CaptchaTimeoutError`. Can be overridden per call.
44
- polling_interval: Seconds to wait between `getTaskResult` polls
45
- inside `solve()`.
44
+ polling_interval: Seconds to wait before the first `getTaskResult`
45
+ poll and between subsequent polls inside `solve()`. Defaults
46
+ to 10 seconds, matching the official 2Captcha Python SDK.
46
47
  language_pool: Default worker pool selector (e.g. `"en"` or `"ru"`)
47
48
  applied to every `create_task()`/`solve()` call that doesn't pass
48
49
  its own `language_pool`. Leave unset to use the account's default
@@ -160,6 +161,7 @@ class CaptchaClient:
160
161
  Raises:
161
162
  ApiError: The API reports an error for this task (e.g. it expired).
162
163
  NetworkError: The request failed at the transport level.
164
+ CaptchaTimeoutError: The HTTP request timed out.
163
165
  """
164
166
  payload = {
165
167
  "clientKey": self.client_key,
@@ -180,6 +182,7 @@ class CaptchaClient:
180
182
  Raises:
181
183
  ApiError: The API key is invalid or the account can't be resolved.
182
184
  NetworkError: The request failed at the transport level.
185
+ CaptchaTimeoutError: The HTTP request timed out.
183
186
  """
184
187
  payload = {"clientKey": self.client_key}
185
188
  data = self._request("getBalance", payload)
@@ -217,14 +220,16 @@ class CaptchaClient:
217
220
 
218
221
  task_id = self.create_task(task, language_pool=language_pool)
219
222
 
220
- deadline = time.time() + (timeout if timeout is not None else self.timeout)
223
+ deadline = time.monotonic() + (timeout if timeout is not None else self.timeout)
221
224
 
222
- while time.time() < deadline:
225
+ while time.monotonic() < deadline:
226
+ remaining = deadline - time.monotonic()
227
+ time.sleep(min(self.polling_interval, max(0, remaining)))
228
+ if time.monotonic() >= deadline:
229
+ break
223
230
  result = self.get_task_result(task_id)
224
231
 
225
232
  if result.get("status") == "ready":
226
233
  return result["solution"]
227
234
 
228
- time.sleep(self.polling_interval)
229
-
230
235
  raise CaptchaTimeoutError("Task solving timed out.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: captcha-solver-api
3
- Version: 1.0.0
3
+ Version: 1.0.1
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
@@ -110,7 +110,7 @@ client = CaptchaClient("your_api_key")
110
110
 
111
111
  ## Quick Start
112
112
 
113
- Solve a reCAPTCHA v2 in 4 lines.
113
+ Solve a reCAPTCHA v2 with a task object and one `solve()` call.
114
114
 
115
115
  ```python
116
116
  from captcha_solver_api import CaptchaClient
@@ -156,8 +156,8 @@ Constructor.
156
156
  |---|---|---|---|
157
157
  | `client_key` | `str` | required | Your Captcha Solver API key. Raises `ValidationError` if empty. |
158
158
  | `base_url` | `str` | `https://api.captcha-solver.com` | API base URL. Override only for self-hosted/staging deployments. |
159
- | `timeout` | `int` | `120` | Default max seconds `solve()` waits for a solution before raising `CaptchaTimeoutError`. Overridable per call. |
160
- | `polling_interval` | `int` | `3` | Seconds between `getTaskResult` polls inside `solve()`. |
159
+ | `timeout` | `int` | `120` | Polling window after task creation, in seconds. Overridable per call; each HTTP request has a separate 30-second timeout. |
160
+ | `polling_interval` | `int` | `10` | Seconds before the first `getTaskResult` poll and between subsequent polls, matching the official 2Captcha Python SDK. |
161
161
  | `language_pool` | `Optional[str]` | `None` | Default worker pool (`"en"` or `"ru"`) applied to every call that doesn't pass its own `language_pool`. |
162
162
 
163
163
  Both clients hold a reusable connection pool (`requests.Session` / `httpx.AsyncClient`)
@@ -191,9 +191,9 @@ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
191
191
  ### `create_task(task, language_pool=None)`
192
192
 
193
193
  Submits `task` and returns its numeric task ID without waiting for a solution.
194
- Same parameters as `solve()`. Use this instead of `solve()` only if you need to
194
+ Accepts `task` and `language_pool`; it does not accept `timeout`. Use this instead of `solve()` if you need to
195
195
  manage polling yourself (e.g. checking on many tasks from a different process).
196
- Raises `ApiError`, `NetworkError`.
196
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
197
197
 
198
198
  ### `get_task_result(task_id)`
199
199
 
@@ -201,12 +201,12 @@ Fetches the current status of a task created with `create_task()`. Always
201
201
  returns a dict with a `status` key (`"processing"` or `"ready"`); when
202
202
  `"ready"`, also has a `solution` dict. This is a single poll, not a wait --
203
203
  call it repeatedly (as `solve()` does) until `status` is `"ready"`.
204
- Raises `ApiError`, `NetworkError`.
204
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
205
205
 
206
206
  ### `get_balance()`
207
207
 
208
208
  Returns the account's current balance (`float`) in the account's currency.
209
- Raises `ApiError`, `NetworkError`.
209
+ Raises `ApiError`, `CaptchaTimeoutError`, or `NetworkError`.
210
210
 
211
211
  ## Captcha Types
212
212
 
@@ -276,8 +276,10 @@ task = RecaptchaV2Task(
276
276
  Use this method to solve the Enterprise version of reCAPTCHA v2 and obtain a
277
277
  token for a page that uses `grecaptcha.enterprise`.
278
278
 
279
- `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask`. Same fields
280
- as reCAPTCHA v2, plus:
279
+ `RecaptchaV2EnterpriseTaskProxyless` / `RecaptchaV2EnterpriseTask` accept
280
+ `websiteURL`, `websiteKey`, `isInvisible`, `apiDomain`, `userAgent`, and `cookies`,
281
+ plus `enterprisePayload`. Use `enterprisePayload={"s": "..."}` for the
282
+ Enterprise `s` value; these classes do not accept `recaptchaDataSValue`.
281
283
 
282
284
  | Parameter | Required | Description |
283
285
  |---|---|---|
@@ -307,8 +309,8 @@ With proxy, use `RecaptchaV2EnterpriseTask` (same proxy fields as reCAPTCHA v2).
307
309
  Use this method to obtain a score-based reCAPTCHA v3 token for a specific site,
308
310
  action, and minimum score. This variant does not use a proxy.
309
311
 
310
- `RecaptchaV3TaskProxyless`. No proxy variant exists -- v3 is score-based and
311
- invisible, so there's no widget/session to pin to a proxy IP.
312
+ `RecaptchaV3TaskProxyless`. This API supports v3 through the service's IPs;
313
+ there is no corresponding task type for your own proxy.
312
314
 
313
315
  | Parameter | Required | Description |
314
316
  |---|---|---|
@@ -353,7 +355,10 @@ that the target page expects in `cf-turnstile-response`.
353
355
  | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. |
354
356
  | `userAgent` | no | User-Agent to solve with -- the returned token is tied to it, submit with the same one. |
355
357
 
356
- **Response:** `token` -- submit as `cf-turnstile-response`.
358
+ **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.
357
362
 
358
363
  ```python
359
364
  from captcha_solver_api import CaptchaClient
@@ -425,7 +430,7 @@ pass the values collected from the target page before creating the task.
425
430
  | `version` | no | `3` (default) or `4`. |
426
431
  | `gt` | v3 only | Public key of the GeeTest widget. |
427
432
  | `challenge` | v3 only | Session-specific challenge value from the page -- must be freshly fetched for every request, it cannot be reused. |
428
- | `initParameters` | v4 only | Extra parameters from the page's `initGeetest` call; for v4 must contain `captcha_id`. |
433
+ | `initParameters` | required for v4; optional for v3 | Extra initialization parameters; for v4 must contain `captcha_id`. |
429
434
  | `geetestApiServerSubdomain` | no | Custom GeeTest API subdomain, if the site uses one. |
430
435
  | `userAgent` | no | User-Agent to solve with. |
431
436
  | `risk_type` | no | Value of the `risk_type` parameter from the captcha-loading request, if present. Dynamic, single-use, and time-limited. |
@@ -598,6 +603,14 @@ it for every other call:
598
603
  result = client.solve(task, timeout=300)
599
604
  ```
600
605
 
606
+ The [API requires at least 5 seconds between polls](https://captcha-solver.com/en/docs/how-it-works).
607
+ The SDK defaults to 10 seconds, like the official 2Captcha Python SDK. `solve()`
608
+ waits for `polling_interval` before its first poll and between polls.
609
+ Its default 120-second polling window is independent of the API's five-minute
610
+ task lifetime. A local timeout does not cancel a task or mean that the API
611
+ failed to solve it. Use `create_task()` and keep its `task_id` if you need to
612
+ check the same task later; calling `solve()` again creates a new task.
613
+
601
614
  ### Worker language pool
602
615
 
603
616
  Set a default `language_pool` once at construction instead of passing it to every call:
@@ -683,9 +696,10 @@ See the dedicated [examples documentation](https://github.com/captcha-solver-api
683
696
  sync/async example list, setup steps, expected results, and placeholder guidance.
684
697
 
685
698
  - **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
686
- `yandex_smartcaptcha_image.py`) run end-to-end with nothing but a valid
699
+ `yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
687
700
  `CAPTCHA_API_KEY` -- they read sample images bundled in
688
701
  [examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
702
+ `python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
689
703
  - **Token captchas** (`recaptcha_v2.py`, `recaptcha_v2_enterprise.py`, `recaptcha_v3.py`,
690
704
  `turnstile.py`, `yandex_smartcaptcha.py`, `geetest_v4.py`, `tencent.py`) use
691
705
  placeholder values (`https://example.com/...`, `YOUR_WEBSITE_KEY`, `YOUR_APP_ID`,
@@ -709,12 +723,11 @@ python examples/sync/image_to_text.py
709
723
  python examples/sync/coordinates.py
710
724
  ```
711
725
 
712
- **Verified against the live API** during development, using real target pages and
713
- real proxy credentials in place of the placeholders shown above: every captcha
714
- type in this SDK -- proxyless and with proxy -- returned a real, correctly-shaped
715
- solution when pointed at a genuine target. `geetest_v3.py`'s request shape was
716
- likewise confirmed correct when given a real, freshly-fetched `gt`/`challenge`
717
- pair.
726
+ Live checks with real parameters have exercised all supported task types.
727
+ Individual tasks may return `ERROR_CAPTCHA_UNSOLVABLE` or exceed the client's
728
+ polling timeout. A `ready` response confirms that the service returned a
729
+ solution; the target website must still accept the token with the matching
730
+ page and session parameters.
718
731
 
719
732
  ## Requirements
720
733
 
@@ -12,4 +12,5 @@ captcha_solver_api.egg-info/PKG-INFO
12
12
  captcha_solver_api.egg-info/SOURCES.txt
13
13
  captcha_solver_api.egg-info/dependency_links.txt
14
14
  captcha_solver_api.egg-info/requires.txt
15
- captcha_solver_api.egg-info/top_level.txt
15
+ captcha_solver_api.egg-info/top_level.txt
16
+ tests/test_polling.py
@@ -0,0 +1,89 @@
1
+ """The documented polling schedule applies to both clients, including the first poll."""
2
+
3
+ from types import SimpleNamespace
4
+ from unittest.mock import AsyncMock
5
+
6
+ import pytest
7
+
8
+ import captcha_solver_api.client as sync_module
9
+ import captcha_solver_api.async_client as async_module
10
+ from captcha_solver_api import CaptchaClient, AsyncCaptchaClient, CaptchaTimeoutError
11
+ from captcha_solver_api.tasks import RecaptchaV2TaskProxyless
12
+
13
+
14
+ class Clock:
15
+ def __init__(self):
16
+ self.now = 0
17
+
18
+ def monotonic(self):
19
+ return self.now
20
+
21
+ def sleep(self, seconds):
22
+ self.now += seconds
23
+
24
+
25
+ CASES = [
26
+ (None, 25, [10, 20], False),
27
+ (2, 20, [2, 4], False),
28
+ (None, 3, [], True),
29
+ (5, 7, [5], True),
30
+ ]
31
+
32
+
33
+ @pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES)
34
+ def test_sync_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out):
35
+ clock = Clock()
36
+ polls = []
37
+ monkeypatch.setattr(sync_module, 'time', clock)
38
+
39
+ def request(endpoint, payload):
40
+ if endpoint == 'createTask':
41
+ assert clock.now == 0
42
+ return {'errorId': 0, 'taskId': 100}
43
+ assert payload['taskId'] == 100
44
+ polls.append(clock.now)
45
+ if len(polls) == 2:
46
+ return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}}
47
+ return {'errorId': 0, 'status': 'processing'}
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')
53
+ if times_out:
54
+ with pytest.raises(CaptchaTimeoutError):
55
+ client.solve(task, timeout=timeout)
56
+ assert clock.now == timeout
57
+ else:
58
+ assert client.solve(task, timeout=timeout) == {'token': 'done'}
59
+ assert polls == expected_polls
60
+
61
+
62
+ @pytest.mark.parametrize('interval,timeout,expected_polls,times_out', CASES)
63
+ async def test_async_polling_schedule(monkeypatch, interval, timeout, expected_polls, times_out):
64
+ clock = Clock()
65
+ polls = []
66
+ monkeypatch.setattr(async_module, 'time', clock)
67
+ monkeypatch.setattr(async_module, 'asyncio', SimpleNamespace(sleep=AsyncMock(side_effect=clock.sleep)))
68
+
69
+ async def request(endpoint, payload):
70
+ if endpoint == 'createTask':
71
+ assert clock.now == 0
72
+ return {'errorId': 0, 'taskId': 100}
73
+ assert payload['taskId'] == 100
74
+ polls.append(clock.now)
75
+ if len(polls) == 2:
76
+ return {'errorId': 0, 'status': 'ready', 'solution': {'token': 'done'}}
77
+ return {'errorId': 0, 'status': 'processing'}
78
+
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')
83
+ if times_out:
84
+ with pytest.raises(CaptchaTimeoutError):
85
+ await client.solve(task, timeout=timeout)
86
+ assert clock.now == timeout
87
+ else:
88
+ assert await client.solve(task, timeout=timeout) == {'token': 'done'}
89
+ assert polls == expected_polls
@@ -1 +0,0 @@
1
- __version__ = "1.0.0"