captcha-solver-api 1.0.0__py3-none-any.whl
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/__init__.py +38 -0
- captcha_solver_api/_version.py +1 -0
- captcha_solver_api/async_client.py +248 -0
- captcha_solver_api/client.py +230 -0
- captcha_solver_api/exceptions.py +41 -0
- captcha_solver_api/py.typed +0 -0
- captcha_solver_api/tasks.py +705 -0
- captcha_solver_api-1.0.0.dist-info/METADATA +730 -0
- captcha_solver_api-1.0.0.dist-info/RECORD +12 -0
- captcha_solver_api-1.0.0.dist-info/WHEEL +5 -0
- captcha_solver_api-1.0.0.dist-info/licenses/LICENSE.md +21 -0
- captcha_solver_api-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Official Python SDK for the Captcha Solver API.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
from captcha_solver_api import CaptchaClient
|
|
6
|
+
from captcha_solver_api.tasks import RecaptchaV2TaskProxyless
|
|
7
|
+
|
|
8
|
+
client = CaptchaClient("YOUR_API_KEY")
|
|
9
|
+
task = RecaptchaV2TaskProxyless(
|
|
10
|
+
websiteURL="https://example.com",
|
|
11
|
+
websiteKey="6Le-xxxxxxxxx"
|
|
12
|
+
)
|
|
13
|
+
result = client.solve(task)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .client import CaptchaClient
|
|
17
|
+
from .async_client import AsyncCaptchaClient
|
|
18
|
+
from .exceptions import (
|
|
19
|
+
CaptchaError,
|
|
20
|
+
ApiError,
|
|
21
|
+
NetworkError,
|
|
22
|
+
CaptchaTimeoutError,
|
|
23
|
+
TimeoutError,
|
|
24
|
+
ValidationError,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from ._version import __version__
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CaptchaClient",
|
|
31
|
+
"AsyncCaptchaClient",
|
|
32
|
+
"CaptchaError",
|
|
33
|
+
"ApiError",
|
|
34
|
+
"NetworkError",
|
|
35
|
+
"CaptchaTimeoutError",
|
|
36
|
+
"TimeoutError",
|
|
37
|
+
"ValidationError",
|
|
38
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async counterpart of captcha_solver_api.client.CaptchaClient -- same endpoints
|
|
3
|
+
(createTask / getTaskResult / getBalance), same method names/arguments,
|
|
4
|
+
`await`ed. Requires httpx.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
from .exceptions import (
|
|
17
|
+
ApiError,
|
|
18
|
+
NetworkError,
|
|
19
|
+
CaptchaTimeoutError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncCaptchaClient:
|
|
25
|
+
"""
|
|
26
|
+
Async client for interacting with the Captcha Solver API.
|
|
27
|
+
|
|
28
|
+
Holds a single, reused `httpx.AsyncClient` connection pool for the
|
|
29
|
+
lifetime of the instance (created once, not per request), so repeated
|
|
30
|
+
calls -- especially the `getTaskResult` polling inside `solve()` --
|
|
31
|
+
reuse the same keep-alive connection instead of paying a fresh TCP/TLS
|
|
32
|
+
handshake every time. Close it with `aclose()` when you're done, or use
|
|
33
|
+
it as an async context manager:
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
async with AsyncCaptchaClient("YOUR_API_KEY") as client:
|
|
37
|
+
result = await client.solve(task)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
client_key: str,
|
|
43
|
+
base_url: str = "https://api.captcha-solver.com",
|
|
44
|
+
timeout: int = 120,
|
|
45
|
+
polling_interval: int = 3,
|
|
46
|
+
language_pool: Optional[str] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Args:
|
|
50
|
+
client_key: Your Captcha Solver API key.
|
|
51
|
+
base_url: API base URL. Override only for self-hosted or staging
|
|
52
|
+
deployments.
|
|
53
|
+
timeout: Default max seconds `solve()` waits for a solution before
|
|
54
|
+
raising `CaptchaTimeoutError`. Can be overridden per call.
|
|
55
|
+
polling_interval: Seconds to wait between `getTaskResult` polls
|
|
56
|
+
inside `solve()`.
|
|
57
|
+
language_pool: Default worker pool selector (e.g. `"en"` or `"ru"`)
|
|
58
|
+
applied to every `create_task()`/`solve()` call that doesn't pass
|
|
59
|
+
its own `language_pool`. Leave unset to use the account's default
|
|
60
|
+
pool.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValidationError: `client_key` is empty.
|
|
64
|
+
"""
|
|
65
|
+
if not client_key:
|
|
66
|
+
raise ValidationError("client_key is required")
|
|
67
|
+
|
|
68
|
+
self.client_key = client_key
|
|
69
|
+
self.base_url = base_url.rstrip("/")
|
|
70
|
+
self.timeout = timeout
|
|
71
|
+
self.polling_interval = polling_interval
|
|
72
|
+
self.language_pool = language_pool
|
|
73
|
+
|
|
74
|
+
self._client = httpx.AsyncClient(
|
|
75
|
+
follow_redirects=True,
|
|
76
|
+
headers={
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
"Accept": "application/json",
|
|
79
|
+
"X-SDK": f"python-sdk/{__version__}",
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def aclose(self) -> None:
|
|
84
|
+
"""Closes the underlying connection pool. Call this when you're done with
|
|
85
|
+
the client, or use it as an async context manager
|
|
86
|
+
(`async with AsyncCaptchaClient(...) as c:`) to have it closed automatically."""
|
|
87
|
+
await self._client.aclose()
|
|
88
|
+
|
|
89
|
+
async def __aenter__(self) -> "AsyncCaptchaClient":
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __aexit__(self, *exc_info: Any) -> None:
|
|
93
|
+
await self.aclose()
|
|
94
|
+
|
|
95
|
+
async def _request(
|
|
96
|
+
self,
|
|
97
|
+
endpoint: str,
|
|
98
|
+
payload: Dict[str, Any],
|
|
99
|
+
) -> Dict[str, Any]:
|
|
100
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
response = await self._client.post(url, json=payload, timeout=30)
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
except httpx.TimeoutException as exc:
|
|
106
|
+
raise CaptchaTimeoutError("Request timed out.") from exc
|
|
107
|
+
except httpx.HTTPError as exc:
|
|
108
|
+
raise NetworkError(str(exc)) from exc
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
data = response.json()
|
|
112
|
+
except ValueError as exc:
|
|
113
|
+
raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc
|
|
114
|
+
|
|
115
|
+
return data
|
|
116
|
+
|
|
117
|
+
def _ensure_success(self, data: Dict[str, Any]) -> None:
|
|
118
|
+
if data.get("errorId", 0) != 0:
|
|
119
|
+
raise ApiError(
|
|
120
|
+
data.get("errorCode", "UNKNOWN_ERROR"),
|
|
121
|
+
data.get("errorDescription", "Unknown API error."),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def create_task(self, task: Any, language_pool: Optional[str] = None) -> int:
|
|
125
|
+
"""Submit a CAPTCHA task and return its ID without waiting for a solution.
|
|
126
|
+
|
|
127
|
+
This calls the API `createTask` endpoint. Use `solve()` for the usual
|
|
128
|
+
submit-and-wait flow, or use this method when polling must be managed
|
|
129
|
+
separately, for example when checking several tasks from another process.
|
|
130
|
+
See the `createTask` and CAPTCHA type details in the
|
|
131
|
+
`https://captcha-solver.com/en/docs/captcha-types` documentation.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
task: A task object from `captcha_solver_api.tasks`.
|
|
135
|
+
language_pool: Optional worker pool selector such as `"en"` or
|
|
136
|
+
`"ru"`. Falls back to the client's configured pool.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The numeric task ID to pass to `get_task_result()`.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ApiError: The API rejected the task or its parameters.
|
|
143
|
+
NetworkError: The request failed at the transport level.
|
|
144
|
+
CaptchaTimeoutError: The HTTP request timed out.
|
|
145
|
+
"""
|
|
146
|
+
payload: Dict[str, Any] = {
|
|
147
|
+
"clientKey": self.client_key,
|
|
148
|
+
"task": task.to_dict(),
|
|
149
|
+
}
|
|
150
|
+
pool = language_pool if language_pool is not None else self.language_pool
|
|
151
|
+
if pool:
|
|
152
|
+
payload["languagePool"] = pool
|
|
153
|
+
|
|
154
|
+
data = await self._request("createTask", payload)
|
|
155
|
+
self._ensure_success(data)
|
|
156
|
+
return data["taskId"]
|
|
157
|
+
|
|
158
|
+
async def get_task_result(self, task_id: int) -> Dict[str, Any]:
|
|
159
|
+
"""Fetch the current status of a previously submitted task.
|
|
160
|
+
|
|
161
|
+
This performs one asynchronous poll of `getTaskResult`; it does not
|
|
162
|
+
wait until the task is ready. Call it repeatedly until the response has
|
|
163
|
+
`status == "ready"`, or use `solve()` to handle polling automatically.
|
|
164
|
+
See `https://captcha-solver.com/en/docs/captcha-types` for the response
|
|
165
|
+
fields returned by each CAPTCHA type.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
task_id: The ID returned by `create_task()`.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
The raw API response with `status`; ready responses also contain a
|
|
172
|
+
solution dictionary.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ApiError: The API reports an error for the task.
|
|
176
|
+
NetworkError: The request failed at the transport level.
|
|
177
|
+
"""
|
|
178
|
+
payload = {
|
|
179
|
+
"clientKey": self.client_key,
|
|
180
|
+
"taskId": task_id,
|
|
181
|
+
}
|
|
182
|
+
data = await self._request("getTaskResult", payload)
|
|
183
|
+
self._ensure_success(data)
|
|
184
|
+
return data
|
|
185
|
+
|
|
186
|
+
async def get_balance(self) -> float:
|
|
187
|
+
"""Fetch the account's current balance asynchronously.
|
|
188
|
+
|
|
189
|
+
Calls the `getBalance` endpoint. See the API documentation at
|
|
190
|
+
`https://captcha-solver.com/en/docs/captcha-types` for account and API
|
|
191
|
+
requirements.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The available balance in the account's currency.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ApiError: The API key is invalid or the account cannot be resolved.
|
|
198
|
+
NetworkError: The request failed at the transport level.
|
|
199
|
+
"""
|
|
200
|
+
payload = {"clientKey": self.client_key}
|
|
201
|
+
data = await self._request("getBalance", payload)
|
|
202
|
+
self._ensure_success(data)
|
|
203
|
+
return data["balance"]
|
|
204
|
+
|
|
205
|
+
async def solve(
|
|
206
|
+
self,
|
|
207
|
+
task: Any,
|
|
208
|
+
language_pool: Optional[str] = None,
|
|
209
|
+
timeout: Optional[int] = None,
|
|
210
|
+
) -> Dict[str, Any]:
|
|
211
|
+
"""Submit a task and asynchronously poll until its solution is ready.
|
|
212
|
+
|
|
213
|
+
This is the main entry point for the async client. It combines
|
|
214
|
+
`create_task()` and repeated `get_task_result()` calls, waiting between
|
|
215
|
+
polls without blocking the event loop. For several concurrent solves,
|
|
216
|
+
create multiple coroutines and await them with `asyncio.gather()`.
|
|
217
|
+
See the CAPTCHA-specific request and response formats at
|
|
218
|
+
`https://captcha-solver.com/en/docs/captcha-types`.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
task: A task object from `captcha_solver_api.tasks`.
|
|
222
|
+
language_pool: Optional worker pool selector. Falls back to the
|
|
223
|
+
client's configured pool.
|
|
224
|
+
timeout: Maximum polling time for this call, in seconds. Overrides
|
|
225
|
+
the client's default timeout.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
The solution dictionary once the task status becomes `"ready"`.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
ApiError: The API rejected the task or reported a solving error.
|
|
232
|
+
CaptchaTimeoutError: No solution was ready before the deadline.
|
|
233
|
+
NetworkError: A request failed at the transport level.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
task_id = await self.create_task(task, language_pool=language_pool)
|
|
237
|
+
|
|
238
|
+
deadline = time.time() + (timeout if timeout is not None else self.timeout)
|
|
239
|
+
|
|
240
|
+
while time.time() < deadline:
|
|
241
|
+
result = await self.get_task_result(task_id)
|
|
242
|
+
|
|
243
|
+
if result.get("status") == "ready":
|
|
244
|
+
return result["solution"]
|
|
245
|
+
|
|
246
|
+
await asyncio.sleep(self.polling_interval)
|
|
247
|
+
|
|
248
|
+
raise CaptchaTimeoutError("Task solving timed out.")
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main API client for the Captcha Solver service.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
from .exceptions import (
|
|
14
|
+
ApiError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
CaptchaTimeoutError,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CaptchaClient:
|
|
22
|
+
"""
|
|
23
|
+
Main client for interacting with the Captcha Solver API.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
client = CaptchaClient("YOUR_API_KEY")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
client_key: str,
|
|
32
|
+
base_url: str = "https://api.captcha-solver.com",
|
|
33
|
+
timeout: int = 120,
|
|
34
|
+
polling_interval: int = 3,
|
|
35
|
+
language_pool: Optional[str] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Args:
|
|
39
|
+
client_key: Your Captcha Solver API key.
|
|
40
|
+
base_url: API base URL. Override only for self-hosted or staging
|
|
41
|
+
deployments.
|
|
42
|
+
timeout: Default max seconds `solve()` waits for a solution before
|
|
43
|
+
raising `CaptchaTimeoutError`. Can be overridden per call.
|
|
44
|
+
polling_interval: Seconds to wait between `getTaskResult` polls
|
|
45
|
+
inside `solve()`.
|
|
46
|
+
language_pool: Default worker pool selector (e.g. `"en"` or `"ru"`)
|
|
47
|
+
applied to every `create_task()`/`solve()` call that doesn't pass
|
|
48
|
+
its own `language_pool`. Leave unset to use the account's default
|
|
49
|
+
pool.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValidationError: `client_key` is empty.
|
|
53
|
+
"""
|
|
54
|
+
if not client_key:
|
|
55
|
+
raise ValidationError("client_key is required")
|
|
56
|
+
|
|
57
|
+
self.client_key = client_key
|
|
58
|
+
self.base_url = base_url.rstrip("/")
|
|
59
|
+
self.timeout = timeout
|
|
60
|
+
self.polling_interval = polling_interval
|
|
61
|
+
self.language_pool = language_pool
|
|
62
|
+
|
|
63
|
+
self.session = requests.Session()
|
|
64
|
+
self.session.headers.update(
|
|
65
|
+
{
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
"Accept": "application/json",
|
|
68
|
+
"X-SDK": f"python-sdk/{__version__}",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def close(self) -> None:
|
|
73
|
+
"""Closes the underlying connection pool. Call this when you're done with
|
|
74
|
+
the client, or use it as a context manager (`with CaptchaClient(...) as c:`)
|
|
75
|
+
to have it closed automatically."""
|
|
76
|
+
self.session.close()
|
|
77
|
+
|
|
78
|
+
def __enter__(self) -> "CaptchaClient":
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def __exit__(self, *exc_info: Any) -> None:
|
|
82
|
+
self.close()
|
|
83
|
+
|
|
84
|
+
def _request(
|
|
85
|
+
self,
|
|
86
|
+
endpoint: str,
|
|
87
|
+
payload: Dict[str, Any],
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
response = self.session.post(url, json=payload, timeout=30)
|
|
93
|
+
response.raise_for_status()
|
|
94
|
+
except requests.exceptions.Timeout as exc:
|
|
95
|
+
raise CaptchaTimeoutError("Request timed out.") from exc
|
|
96
|
+
except requests.exceptions.RequestException as exc:
|
|
97
|
+
raise NetworkError(str(exc)) from exc
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
data = response.json()
|
|
101
|
+
except ValueError as exc:
|
|
102
|
+
raise NetworkError(f"Non-JSON response from API: {response.text[:200]!r}") from exc
|
|
103
|
+
|
|
104
|
+
return data
|
|
105
|
+
|
|
106
|
+
def _ensure_success(self, data: Dict[str, Any]) -> None:
|
|
107
|
+
if data.get("errorId", 0) != 0:
|
|
108
|
+
raise ApiError(
|
|
109
|
+
data.get("errorCode", "UNKNOWN_ERROR"),
|
|
110
|
+
data.get("errorDescription", "Unknown API error."),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def create_task(self, task: Any, language_pool: Optional[str] = None) -> int:
|
|
114
|
+
"""Submits a captcha task and returns its task ID without waiting for a solution.
|
|
115
|
+
|
|
116
|
+
Calls the `createTask` endpoint. Prefer `solve()` unless you need to manage
|
|
117
|
+
polling yourself (e.g. to check on many tasks from a different process).
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
task: One of the task objects from `captcha_solver_api.tasks` (e.g.
|
|
121
|
+
`RecaptchaV2TaskProxyless`, `ImageToTextTask`).
|
|
122
|
+
language_pool: Worker pool selector, e.g. `"en"` or `"ru"`. Falls back
|
|
123
|
+
to the client's `language_pool` (set at construction) when omitted.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The numeric task ID to pass to `get_task_result()`.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ApiError: The API rejected the task (bad key, bad parameters, etc).
|
|
130
|
+
NetworkError: The request failed at the transport level.
|
|
131
|
+
CaptchaTimeoutError: The HTTP request itself timed out (not the solve).
|
|
132
|
+
"""
|
|
133
|
+
payload: Dict[str, Any] = {
|
|
134
|
+
"clientKey": self.client_key,
|
|
135
|
+
"task": task.to_dict(),
|
|
136
|
+
}
|
|
137
|
+
pool = language_pool if language_pool is not None else self.language_pool
|
|
138
|
+
if pool:
|
|
139
|
+
payload["languagePool"] = pool
|
|
140
|
+
|
|
141
|
+
data = self._request("createTask", payload)
|
|
142
|
+
self._ensure_success(data)
|
|
143
|
+
return data["taskId"]
|
|
144
|
+
|
|
145
|
+
def get_task_result(self, task_id: int) -> Dict[str, Any]:
|
|
146
|
+
"""Fetches the current status of a task created with `create_task()`.
|
|
147
|
+
|
|
148
|
+
Calls the `getTaskResult` endpoint. This is a single poll, not a wait --
|
|
149
|
+
call it repeatedly (as `solve()` does) until `status` is `"ready"`.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
task_id: The ID returned by `create_task()`.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The raw API response. Always has a `status` key (`"processing"` or
|
|
156
|
+
`"ready"`); when `"ready"`, also has a `solution` dict whose shape
|
|
157
|
+
depends on the task type (e.g. `{"gRecaptchaResponse": "..."}` for
|
|
158
|
+
reCAPTCHA, `{"text": "..."}` for `ImageToTextTask`).
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ApiError: The API reports an error for this task (e.g. it expired).
|
|
162
|
+
NetworkError: The request failed at the transport level.
|
|
163
|
+
"""
|
|
164
|
+
payload = {
|
|
165
|
+
"clientKey": self.client_key,
|
|
166
|
+
"taskId": task_id,
|
|
167
|
+
}
|
|
168
|
+
data = self._request("getTaskResult", payload)
|
|
169
|
+
self._ensure_success(data)
|
|
170
|
+
return data
|
|
171
|
+
|
|
172
|
+
def get_balance(self) -> float:
|
|
173
|
+
"""Fetches the account's current balance.
|
|
174
|
+
|
|
175
|
+
Calls the `getBalance` endpoint.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
The available balance, in the account's currency.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ApiError: The API key is invalid or the account can't be resolved.
|
|
182
|
+
NetworkError: The request failed at the transport level.
|
|
183
|
+
"""
|
|
184
|
+
payload = {"clientKey": self.client_key}
|
|
185
|
+
data = self._request("getBalance", payload)
|
|
186
|
+
self._ensure_success(data)
|
|
187
|
+
return data["balance"]
|
|
188
|
+
|
|
189
|
+
def solve(
|
|
190
|
+
self,
|
|
191
|
+
task: Any,
|
|
192
|
+
language_pool: Optional[str] = None,
|
|
193
|
+
timeout: Optional[int] = None,
|
|
194
|
+
) -> Dict[str, Any]:
|
|
195
|
+
"""Submits `task` and polls until it's solved. This is the main entry point --
|
|
196
|
+
it wraps `create_task()` and `get_task_result()` so you don't have to poll
|
|
197
|
+
by hand.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
task: One of the task objects from `captcha_solver_api.tasks`.
|
|
201
|
+
language_pool: Worker pool selector, e.g. `"en"` or `"ru"`. Falls back
|
|
202
|
+
to the client's `language_pool` (set at construction) when omitted.
|
|
203
|
+
timeout: Overrides the client's default polling timeout for this call
|
|
204
|
+
only (useful for captcha types that reliably take longer, e.g.
|
|
205
|
+
classic reCAPTCHA v2), in seconds.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
The `solution` dict once `status` is `"ready"`. Its shape depends on
|
|
209
|
+
the task type -- see the per-type docstrings in `captcha_solver_api.tasks`
|
|
210
|
+
or the README's method reference.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
ApiError: The API rejected the task or reported an error while solving.
|
|
214
|
+
CaptchaTimeoutError: No solution was ready before the deadline.
|
|
215
|
+
NetworkError: A request failed at the transport level.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
task_id = self.create_task(task, language_pool=language_pool)
|
|
219
|
+
|
|
220
|
+
deadline = time.time() + (timeout if timeout is not None else self.timeout)
|
|
221
|
+
|
|
222
|
+
while time.time() < deadline:
|
|
223
|
+
result = self.get_task_result(task_id)
|
|
224
|
+
|
|
225
|
+
if result.get("status") == "ready":
|
|
226
|
+
return result["solution"]
|
|
227
|
+
|
|
228
|
+
time.sleep(self.polling_interval)
|
|
229
|
+
|
|
230
|
+
raise CaptchaTimeoutError("Task solving timed out.")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions used by the Captcha Solver SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CaptchaError(Exception):
|
|
7
|
+
"""Base exception for all SDK errors."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NetworkError(CaptchaError):
|
|
13
|
+
"""Raised when a network request fails."""
|
|
14
|
+
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CaptchaTimeoutError(CaptchaError):
|
|
19
|
+
"""Raised when the operation exceeds the configured timeout."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Deprecated alias kept for backward compatibility. It shadows the built-in
|
|
25
|
+
# ``TimeoutError`` when imported by name, so prefer ``CaptchaTimeoutError``.
|
|
26
|
+
TimeoutError = CaptchaTimeoutError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ApiError(CaptchaError):
|
|
30
|
+
"""Raised when the API returns an error."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, error_code: str, error_description: str) -> None:
|
|
33
|
+
self.error_code = error_code
|
|
34
|
+
self.error_description = error_description
|
|
35
|
+
super().__init__(f"{error_code}: {error_description}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ValidationError(CaptchaError):
|
|
39
|
+
"""Raised for client-side argument problems caught before any request is sent."""
|
|
40
|
+
|
|
41
|
+
pass
|
|
File without changes
|