solvecaptcha 0.1.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.
@@ -0,0 +1,13 @@
1
+ """SolveCaptcha Python Client."""
2
+
3
+ from .client import DEFAULT_SOFT_ID, SolveCaptchaClient
4
+ from .errors import SolveCaptchaError
5
+ from .types import CaptchaTask, GeeTestResult
6
+
7
+ __all__ = [
8
+ "DEFAULT_SOFT_ID",
9
+ "CaptchaTask",
10
+ "GeeTestResult",
11
+ "SolveCaptchaClient",
12
+ "SolveCaptchaError",
13
+ ]
solvecaptcha/client.py ADDED
@@ -0,0 +1,510 @@
1
+ """SolveCaptcha API client implementation."""
2
+
3
+ import json
4
+ import time
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from .errors import ERROR_DESCRIPTIONS, SolveCaptchaError
10
+ from .types import GeeTestResult
11
+
12
+ DEFAULT_SOFT_ID = ""
13
+ DEFAULT_BASE_URL = "https://api.solvecaptcha.com"
14
+
15
+
16
+ class SolveCaptchaClient:
17
+ """Client for the SolveCaptcha human and AI captcha solving service.
18
+
19
+ Args:
20
+ api_key: SolveCaptcha API key.
21
+ base_url: Base URL of the API. Defaults to https://api.solvecaptcha.com.
22
+ timeout: HTTP request timeout in seconds. Defaults to 30.0.
23
+ soft_id: Optional software/partner identifier.
24
+ default_polling_timeout: Default timeout in seconds when waiting for solutions.
25
+ default_polling_interval: Default interval in seconds between status polls.
26
+
27
+ Examples:
28
+ >>> from solvecaptcha import SolveCaptchaClient
29
+ >>> client = SolveCaptchaClient(api_key="your_api_key")
30
+ >>> balance = client.get_balance()
31
+ >>> print(balance)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ api_key: str,
37
+ base_url: str = DEFAULT_BASE_URL,
38
+ timeout: float = 30.0,
39
+ soft_id: str | int | None = None,
40
+ default_polling_timeout: float = 120.0,
41
+ default_polling_interval: float = 5.0,
42
+ ) -> None:
43
+ if not api_key or not api_key.strip():
44
+ raise SolveCaptchaError(
45
+ "api_key is required to initialize SolveCaptchaClient", "ERROR_KEY_DOES_NOT_EXIST"
46
+ )
47
+ self.api_key = api_key.strip()
48
+ self.base_url = base_url.rstrip("/")
49
+ self.soft_id = str(soft_id) if soft_id is not None else DEFAULT_SOFT_ID
50
+ self.polling_timeout = default_polling_timeout
51
+ self.polling_interval = default_polling_interval
52
+ self._client = httpx.Client(base_url=self.base_url, timeout=timeout)
53
+
54
+ def __enter__(self) -> "SolveCaptchaClient":
55
+ return self
56
+
57
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
58
+ self.close()
59
+
60
+ def close(self) -> None:
61
+ """Closes the underlying HTTP client session."""
62
+ self._client.close()
63
+
64
+ def get_balance(self) -> float:
65
+ """Retrieves the current account balance in USD.
66
+
67
+ Returns:
68
+ The account balance as a float.
69
+
70
+ Raises:
71
+ SolveCaptchaError: If the server returns an error or unexpected response.
72
+
73
+ Examples:
74
+ >>> client = SolveCaptchaClient(api_key="key")
75
+ >>> balance = client.get_balance()
76
+ >>> print(f"Balance: ${balance:.4f}")
77
+ """
78
+ raw = self._query_result({"action": "getbalance"})
79
+ try:
80
+ return float(raw)
81
+ except ValueError as exc:
82
+ raise SolveCaptchaError(f"Failed to parse balance: {raw}", "PARSE_ERROR", raw) from exc
83
+
84
+ def solve_image(
85
+ self,
86
+ body: str,
87
+ phrase: int | None = None,
88
+ regsense: int | None = None,
89
+ numeric: int | None = None,
90
+ calc: int | None = None,
91
+ min_len: int | None = None,
92
+ max_len: int | None = None,
93
+ language: int | None = None,
94
+ textinstructions: str | None = None,
95
+ ) -> str:
96
+ """Solves a normal image-based captcha from base64 data.
97
+
98
+ Args:
99
+ body: Base64-encoded image string.
100
+ phrase: 0 = single word, 1 = multiple words.
101
+ regsense: 0 = case-insensitive, 1 = case-sensitive.
102
+ numeric: Character set constraint (0..4).
103
+ calc: 1 if math calculation is required.
104
+ min_len: Minimum character length.
105
+ max_len: Maximum character length.
106
+ language: 0 = default, 1 = Cyrillic, 2 = Latin.
107
+ textinstructions: Instructions for human worker.
108
+
109
+ Returns:
110
+ Solved text from the image.
111
+
112
+ Raises:
113
+ SolveCaptchaError: If submission fails or times out.
114
+
115
+ Examples:
116
+ >>> client = SolveCaptchaClient(api_key="key")
117
+ >>> text = client.solve_image(body="iVBORw0KGgoAAAANSUhEUgAA...")
118
+ >>> print("Captcha text:", text)
119
+ """
120
+ params: dict[str, Any] = {"method": "base64", "body": body}
121
+ if phrase is not None:
122
+ params["phrase"] = phrase
123
+ if regsense is not None:
124
+ params["regsense"] = regsense
125
+ if numeric is not None:
126
+ params["numeric"] = numeric
127
+ if calc is not None:
128
+ params["calc"] = calc
129
+ if min_len is not None:
130
+ params["min_len"] = min_len
131
+ if max_len is not None:
132
+ params["max_len"] = max_len
133
+ if language is not None:
134
+ params["language"] = language
135
+ if textinstructions is not None:
136
+ params["textinstructions"] = textinstructions
137
+
138
+ task_id = self.create_task(params)
139
+ return self.wait_for_result(task_id)
140
+
141
+ def solve_text(self, text: str, language: int | None = None) -> str:
142
+ """Solves a text question captcha.
143
+
144
+ Args:
145
+ text: Question string to solve.
146
+ language: 0 = default, 1 = Cyrillic, 2 = Latin.
147
+
148
+ Returns:
149
+ Text answer to the question.
150
+
151
+ Examples:
152
+ >>> client = SolveCaptchaClient(api_key="key")
153
+ >>> answer = client.solve_text("If tomorrow is Saturday, what day is today?")
154
+ >>> print("Answer:", answer)
155
+ """
156
+ params: dict[str, Any] = {"textcaptcha": text}
157
+ if language is not None:
158
+ params["language"] = language
159
+
160
+ task_id = self.create_task(params)
161
+ return self.wait_for_result(task_id)
162
+
163
+ def solve_recaptcha_v2(
164
+ self,
165
+ googlekey: str,
166
+ pageurl: str,
167
+ invisible: bool = False,
168
+ data_s: str | None = None,
169
+ ) -> str:
170
+ """Solves Google reCAPTCHA V2.
171
+
172
+ Args:
173
+ googlekey: Sitekey extracted from target page.
174
+ pageurl: Full URL of the page with the reCAPTCHA.
175
+ invisible: True if widget is invisible.
176
+ data_s: Optional 'data-s' parameter value.
177
+
178
+ Returns:
179
+ reCAPTCHA token solution string.
180
+
181
+ Examples:
182
+ >>> client = SolveCaptchaClient(api_key="key")
183
+ >>> token = client.solve_recaptcha_v2(
184
+ ... googlekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
185
+ ... pageurl="https://example.com/login",
186
+ ... )
187
+ """
188
+ params: dict[str, Any] = {
189
+ "method": "userrecaptcha",
190
+ "googlekey": googlekey,
191
+ "pageurl": pageurl,
192
+ }
193
+ if invisible:
194
+ params["invisible"] = 1
195
+ if data_s:
196
+ params["data-s"] = data_s
197
+
198
+ task_id = self.create_task(params)
199
+ return self.wait_for_result(task_id)
200
+
201
+ def solve_recaptcha_v3(
202
+ self,
203
+ googlekey: str,
204
+ pageurl: str,
205
+ action: str | None = None,
206
+ min_score: float | None = None,
207
+ ) -> str:
208
+ """Solves Google reCAPTCHA V3.
209
+
210
+ Args:
211
+ googlekey: Sitekey extracted from target page.
212
+ pageurl: Full URL of the page.
213
+ action: Action parameter string.
214
+ min_score: Minimum required score threshold (e.g. 0.7).
215
+
216
+ Returns:
217
+ reCAPTCHA token solution string.
218
+
219
+ Examples:
220
+ >>> client = SolveCaptchaClient(api_key="key")
221
+ >>> token = client.solve_recaptcha_v3(
222
+ ... googlekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
223
+ ... pageurl="https://example.com/page",
224
+ ... action="verify",
225
+ ... )
226
+ """
227
+ params: dict[str, Any] = {
228
+ "method": "userrecaptcha",
229
+ "version": "v3",
230
+ "googlekey": googlekey,
231
+ "pageurl": pageurl,
232
+ }
233
+ if action:
234
+ params["action"] = action
235
+ if min_score is not None:
236
+ params["min_score"] = min_score
237
+
238
+ task_id = self.create_task(params)
239
+ return self.wait_for_result(task_id)
240
+
241
+ def solve_turnstile(
242
+ self,
243
+ sitekey: str,
244
+ pageurl: str,
245
+ action: str | None = None,
246
+ data: str | None = None,
247
+ pagedata: str | None = None,
248
+ ) -> str:
249
+ """Solves Cloudflare Turnstile captcha.
250
+
251
+ Args:
252
+ sitekey: Turnstile sitekey.
253
+ pageurl: URL of the protected page.
254
+ action: Optional action parameter.
255
+ data: Optional cData parameter.
256
+ pagedata: Optional pagedata parameter.
257
+
258
+ Returns:
259
+ Turnstile token solution string.
260
+
261
+ Examples:
262
+ >>> client = SolveCaptchaClient(api_key="key")
263
+ >>> token = client.solve_turnstile(
264
+ ... sitekey="0x4AAAAAAABnEBq1w7gYv_m-",
265
+ ... pageurl="https://example.com",
266
+ ... )
267
+ """
268
+ params: dict[str, Any] = {
269
+ "method": "turnstile",
270
+ "sitekey": sitekey,
271
+ "pageurl": pageurl,
272
+ }
273
+ if action:
274
+ params["action"] = action
275
+ if data:
276
+ params["data"] = data
277
+ if pagedata:
278
+ params["pagedata"] = pagedata
279
+
280
+ task_id = self.create_task(params)
281
+ return self.wait_for_result(task_id)
282
+
283
+ def solve_funcaptcha(
284
+ self,
285
+ publickey: str,
286
+ pageurl: str,
287
+ surl: str | None = None,
288
+ data: dict[str, Any] | str | None = None,
289
+ ) -> str:
290
+ """Solves Arkose Labs FunCaptcha.
291
+
292
+ Args:
293
+ publickey: Public key of the widget.
294
+ pageurl: URL containing the FunCaptcha.
295
+ surl: Custom service URL.
296
+ data: Additional data payload.
297
+
298
+ Returns:
299
+ FunCaptcha solution token.
300
+
301
+ Examples:
302
+ >>> client = SolveCaptchaClient(api_key="key")
303
+ >>> token = client.solve_funcaptcha(
304
+ ... publickey="69A21A01-CC43-44CC-A7DC-3E088835E78D",
305
+ ... pageurl="https://example.com/signup",
306
+ ... )
307
+ """
308
+ params: dict[str, Any] = {
309
+ "method": "funcaptcha",
310
+ "publickey": publickey,
311
+ "pageurl": pageurl,
312
+ }
313
+ if surl:
314
+ params["surl"] = surl
315
+ if data:
316
+ params["data"] = json.dumps(data) if isinstance(data, dict) else str(data)
317
+
318
+ task_id = self.create_task(params)
319
+ return self.wait_for_result(task_id)
320
+
321
+ def solve_geetest(
322
+ self,
323
+ gt: str,
324
+ challenge: str,
325
+ pageurl: str,
326
+ api_server: str | None = None,
327
+ ) -> GeeTestResult:
328
+ """Solves GeeTest captcha.
329
+
330
+ Args:
331
+ gt: GeeTest 'gt' token.
332
+ challenge: GeeTest 'challenge' token.
333
+ pageurl: URL of the page containing GeeTest.
334
+ api_server: Optional custom api_server domain.
335
+
336
+ Returns:
337
+ GeeTestResult with challenge, validate, and seccode.
338
+
339
+ Examples:
340
+ >>> client = SolveCaptchaClient(api_key="key")
341
+ >>> res = client.solve_geetest(
342
+ ... gt="022397c99c9d64352069eab73bc373d7",
343
+ ... challenge="3049170ffb08bb6b907572ca35489f07",
344
+ ... pageurl="https://example.com/geetest",
345
+ ... )
346
+ >>> print(res.validate)
347
+ """
348
+ params: dict[str, Any] = {
349
+ "method": "geetest",
350
+ "gt": gt,
351
+ "challenge": challenge,
352
+ "pageurl": pageurl,
353
+ }
354
+ if api_server:
355
+ params["api_server"] = api_server
356
+
357
+ task_id = self.create_task(params)
358
+ raw = self.wait_for_result(task_id)
359
+
360
+ try:
361
+ parsed = json.loads(raw)
362
+ return GeeTestResult(
363
+ challenge=parsed["challenge"],
364
+ validate=parsed["validate"],
365
+ seccode=parsed["seccode"],
366
+ )
367
+ except (json.JSONDecodeError, KeyError) as exc:
368
+ raise SolveCaptchaError(
369
+ f"Failed to parse GeeTest solution: {raw}", "PARSE_ERROR", raw
370
+ ) from exc
371
+
372
+ def create_task(self, params: dict[str, Any]) -> str:
373
+ """Submits a captcha solving task to /in.php.
374
+
375
+ Args:
376
+ params: Parameters specifying captcha type and payload.
377
+
378
+ Returns:
379
+ Task ID string.
380
+
381
+ Raises:
382
+ SolveCaptchaError: If task submission is rejected.
383
+ """
384
+ payload: dict[str, Any] = {
385
+ "key": self.api_key,
386
+ "json": 1,
387
+ **params,
388
+ }
389
+ if self.soft_id and "soft_id" not in payload:
390
+ payload["soft_id"] = self.soft_id
391
+
392
+ try:
393
+ resp = self._client.post("/in.php", params=payload)
394
+ resp.raise_for_status()
395
+ data = resp.json()
396
+
397
+ if isinstance(data, dict):
398
+ if data.get("status") == 1 and data.get("request"):
399
+ return str(data["request"])
400
+ if data.get("status") == 0 and data.get("request"):
401
+ code = str(data["request"])
402
+ desc = ERROR_DESCRIPTIONS.get(code, f"SolveCaptcha error: {code}")
403
+ raise SolveCaptchaError(desc, code=code, response_body=data)
404
+
405
+ raise SolveCaptchaError(
406
+ f"Unexpected /in.php response: {resp.text}", "INVALID_RESPONSE", data
407
+ )
408
+ except httpx.HTTPError as exc:
409
+ raise SolveCaptchaError(f"create_task request failed: {exc}", "NETWORK_ERROR") from exc
410
+
411
+ def get_task_result(self, task_id: str) -> str | None:
412
+ """Checks the resolution status of an active task.
413
+
414
+ Args:
415
+ task_id: Task ID returned by create_task.
416
+
417
+ Returns:
418
+ Solution string or None if still processing.
419
+ """
420
+ try:
421
+ return self._query_result({"action": "get", "id": task_id})
422
+ except SolveCaptchaError as exc:
423
+ if exc.code == "CAPCHA_NOT_READY":
424
+ return None
425
+ raise
426
+
427
+ def wait_for_result(
428
+ self,
429
+ task_id: str,
430
+ timeout: float | None = None,
431
+ interval: float | None = None,
432
+ ) -> str:
433
+ """Polls until a task is solved or timeout occurs.
434
+
435
+ Args:
436
+ task_id: Task ID.
437
+ timeout: Maximum seconds to wait.
438
+ interval: Polling frequency in seconds.
439
+
440
+ Returns:
441
+ Solved captcha response string.
442
+
443
+ Raises:
444
+ SolveCaptchaError: If timed out or task encounters an error.
445
+ """
446
+ effective_timeout = timeout if timeout is not None else self.polling_timeout
447
+ effective_interval = interval if interval is not None else self.polling_interval
448
+ start = time.time()
449
+
450
+ while time.time() - start < effective_timeout:
451
+ res = self.get_task_result(task_id)
452
+ if res is not None:
453
+ return res
454
+ time.sleep(effective_interval)
455
+
456
+ raise SolveCaptchaError(
457
+ f"Captcha solving timed out after {effective_timeout}s",
458
+ "TIMEOUT",
459
+ )
460
+
461
+ def report_good(self, task_id: str) -> bool:
462
+ """Reports a correctly solved captcha.
463
+
464
+ Args:
465
+ task_id: Task ID.
466
+
467
+ Returns:
468
+ True if report was recorded.
469
+ """
470
+ try:
471
+ res = self._query_result({"action": "reportgood", "id": task_id})
472
+ return res == "OK_REPORT_RECORDED"
473
+ except SolveCaptchaError:
474
+ return False
475
+
476
+ def report_bad(self, task_id: str) -> bool:
477
+ """Reports an incorrectly solved captcha.
478
+
479
+ Args:
480
+ task_id: Task ID.
481
+
482
+ Returns:
483
+ True if report was recorded.
484
+ """
485
+ try:
486
+ res = self._query_result({"action": "reportbad", "id": task_id})
487
+ return res == "OK_REPORT_RECORDED"
488
+ except SolveCaptchaError:
489
+ return False
490
+
491
+ def _query_result(self, params: dict[str, Any]) -> str:
492
+ query = {"key": self.api_key, "json": 1, **params}
493
+ try:
494
+ resp = self._client.get("/res.php", params=query)
495
+ resp.raise_for_status()
496
+ data = resp.json()
497
+
498
+ if isinstance(data, dict):
499
+ if data.get("status") == 1 and data.get("request") is not None:
500
+ return str(data["request"])
501
+ if data.get("status") == 0 and data.get("request"):
502
+ code = str(data["request"])
503
+ desc = ERROR_DESCRIPTIONS.get(code, f"SolveCaptcha error: {code}")
504
+ raise SolveCaptchaError(desc, code=code, response_body=data)
505
+
506
+ raise SolveCaptchaError(
507
+ f"Unexpected /res.php response: {resp.text}", "INVALID_RESPONSE", data
508
+ )
509
+ except httpx.HTTPError as exc:
510
+ raise SolveCaptchaError(f"HTTP request failed: {exc}", "NETWORK_ERROR") from exc
solvecaptcha/errors.py ADDED
@@ -0,0 +1,33 @@
1
+ """Error definitions for the SolveCaptcha client."""
2
+
3
+ from typing import Any
4
+
5
+ ERROR_DESCRIPTIONS: dict[str, str] = {
6
+ "ERROR_WRONG_USER_KEY": "API key is invalid or formatted incorrectly",
7
+ "ERROR_KEY_DOES_NOT_EXIST": "API key not found in SolveCaptcha system",
8
+ "ERROR_ZERO_BALANCE": "Account balance is zero or depleted",
9
+ "ERROR_NO_SLOT_AVAILABLE": "No idle workers available at the moment",
10
+ "ERROR_ZERO_CAPTCHA_FILESIZE": "Uploaded image file size is 0 bytes",
11
+ "ERROR_TOO_BIG_CAPTCHA_FILESIZE": "Uploaded image exceeds maximum size limit (100 KB)",
12
+ "ERROR_WRONG_FILE_EXTENSION": "File extension is not supported",
13
+ "ERROR_IMAGE_TYPE_NOT_SUPPORTED": "Image format cannot be loaded or is corrupted",
14
+ "ERROR_IP_NOT_ALLOWED": "Your IP address is not whitelisted or is blocked",
15
+ "IP_BANNED": "Your IP address has been temporarily banned due to excessive failed attempts",
16
+ "ERROR_CAPTCHA_UNSOLVABLE": "Worker could not solve the submitted captcha",
17
+ "ERROR_BAD_DUPLICATES": "Maximum duplicate requests limit reached",
18
+ "REPORT_NOT_RECORDED": "Feedback report could not be recorded",
19
+ }
20
+
21
+
22
+ class SolveCaptchaError(Exception):
23
+ """Exception raised when a SolveCaptcha API request or task fails."""
24
+
25
+ def __init__(
26
+ self,
27
+ message: str,
28
+ code: str | None = None,
29
+ response_body: Any | None = None,
30
+ ) -> None:
31
+ super().__init__(message)
32
+ self.code = code
33
+ self.response_body = response_body
solvecaptcha/types.py ADDED
@@ -0,0 +1,20 @@
1
+ """Data types and models for the SolveCaptcha API."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class GeeTestResult:
8
+ """Result for GeeTest captcha solutions."""
9
+
10
+ challenge: str
11
+ validate: str
12
+ seccode: str
13
+
14
+
15
+ @dataclass
16
+ class CaptchaTask:
17
+ """Represents an ongoing captcha solving task."""
18
+
19
+ task_id: str
20
+ solution: str | None = None
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.5
2
+ Name: solvecaptcha
3
+ Version: 0.1.0
4
+ Summary: Python client for the SolveCaptcha human and AI captcha solving API
5
+ Project-URL: Repository, https://github.com/TysikGG/api-kit
6
+ Project-URL: Documentation, https://solvecaptcha.com/captcha-solver-api
7
+ License-Expression: ISC
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # solvecaptcha
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/solvecaptcha.svg)](https://pypi.org/project/solvecaptcha/)
16
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
17
+ [![Python Version](https://img.shields.io/pypi/pyversions/solvecaptcha.svg)](https://pypi.org/project/solvecaptcha/)
18
+
19
+ Modern, fully typed Python client library for the [SolveCaptcha](https://solvecaptcha.com/captcha-solver-api) recognition API.
20
+
21
+ Official API documentation: <https://solvecaptcha.com/captcha-solver-api>
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - ⚡ **Multi-Captcha Support** — Cloudflare Turnstile, Google reCAPTCHA v2 / v3, Arkose FunCaptcha, GeeTest v3, normal image captchas, and text questions.
28
+ - 🔄 **Built-in Auto Polling** — High-level methods like `solve_turnstile()` and `solve_recaptcha_v2()` automatically poll until resolved.
29
+ - 🐍 **Modern Pythonic Design** — Type annotations, dataclasses, context manager support (`with SolveCaptchaClient(...) as client:`), and custom exceptions.
30
+ - 🛠️ **Asynchronous Task Queue** — Low-level `create_task()` and `get_task_result()` for custom background queue workers.
31
+ - ⚖️ **Accuracy Reports** — `report_good()` and `report_bad()` for solving quality feedback and automated credit refunds.
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install solvecaptcha
39
+ ```
40
+
41
+ Requires Python 3.10 or higher.
42
+
43
+ ---
44
+
45
+ ## Quick Start (Safe Check)
46
+
47
+ The quick check only queries your account balance. It does not create tasks or consume credits.
48
+
49
+ ```python
50
+ import os
51
+ from solvecaptcha import SolveCaptchaClient
52
+
53
+ client = SolveCaptchaClient(api_key=os.environ["SOLVECAPTCHA_API_KEY"])
54
+
55
+ balance = client.get_balance()
56
+ print(f"Current SolveCaptcha balance: ${balance:.4f} USD")
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Usage Guide
62
+
63
+ ### 1. Solving Cloudflare Turnstile
64
+
65
+ Solve Cloudflare Turnstile challenges and retrieve the verification token:
66
+
67
+ ```python
68
+ from solvecaptcha import SolveCaptchaClient
69
+
70
+ with SolveCaptchaClient(api_key="your_api_key") as client:
71
+ token = client.solve_turnstile(
72
+ sitekey="0x4AAAAAAADnPIDROrmt1Wwj",
73
+ pageurl="https://example.com/protected",
74
+ action="login", # optional
75
+ )
76
+ print("Turnstile token:", token)
77
+ ```
78
+
79
+ ### 2. Solving Google reCAPTCHA v2 & v3
80
+
81
+ Solve checkbox or invisible reCAPTCHA v2, or score-based reCAPTCHA v3:
82
+
83
+ ```python
84
+ with SolveCaptchaClient(api_key="your_api_key") as client:
85
+ # reCAPTCHA v2
86
+ token_v2 = client.solve_recaptcha_v2(
87
+ googlekey="6Le-wvkSAAAAAPBMRTvw0Q4CeHg-tUnSQVwyYcxQ",
88
+ pageurl="https://example.com/protected",
89
+ invisible=0,
90
+ )
91
+ print("reCAPTCHA v2 token:", token_v2)
92
+
93
+ # reCAPTCHA v3
94
+ token_v3 = client.solve_recaptcha_v3(
95
+ googlekey="6LdyC2cUAAAAABlzY2Hg-tUnSQVwyYcxQ",
96
+ pageurl="https://example.com/checkout",
97
+ action="checkout",
98
+ min_score=0.7,
99
+ )
100
+ print("reCAPTCHA v3 token:", token_v3)
101
+ ```
102
+
103
+ ### 3. Solving Normal Image CAPTCHA
104
+
105
+ Solve distorted image captchas by passing a base64-encoded string:
106
+
107
+ ```python
108
+ import base64
109
+ from solvecaptcha import SolveCaptchaClient
110
+
111
+ with open("captcha.png", "rb") as f:
112
+ b64_image = base64.b64encode(f.read()).decode("utf-8")
113
+
114
+ with SolveCaptchaClient(api_key="your_api_key") as client:
115
+ solution_text = client.solve_image(
116
+ body=b64_image,
117
+ numeric=1, # numbers only
118
+ min_len=4,
119
+ max_len=6,
120
+ )
121
+ print("Solved captcha text:", solution_text)
122
+ ```
123
+
124
+ ### 4. Low-Level Task Flow
125
+
126
+ For asynchronous background tasks or task queues:
127
+
128
+ ```python
129
+ import time
130
+ from solvecaptcha import SolveCaptchaClient
131
+
132
+ with SolveCaptchaClient(api_key="your_api_key") as client:
133
+ # 1. Create task
134
+ task_id = client.create_task({
135
+ "method": "turnstile",
136
+ "sitekey": "0x4AAAAAAADnPIDROrmt1Wwj",
137
+ "pageurl": "https://example.com",
138
+ })
139
+ print(f"Task submitted: {task_id}")
140
+
141
+ # 2. Poll manually or wait
142
+ token = None
143
+ for _ in range(30):
144
+ time.sleep(5)
145
+ token = client.get_task_result(task_id)
146
+ if token:
147
+ break
148
+
149
+ if token:
150
+ print("Solved:", token)
151
+ client.report_good(task_id)
152
+ else:
153
+ print("Failed to resolve in time.")
154
+ ```
155
+
156
+ ---
157
+
158
+ ## API Reference
159
+
160
+ ### `SolveCaptchaClient`
161
+
162
+ #### Constructor
163
+
164
+ ```python
165
+ SolveCaptchaClient(
166
+ api_key: str,
167
+ base_url: str = "https://solvecaptcha.com",
168
+ timeout: float = 30.0,
169
+ default_polling_timeout: float = 120.0,
170
+ default_polling_interval: float = 5.0,
171
+ )
172
+ ```
173
+
174
+ | Parameter | Type | Default | Description |
175
+ | :--- | :--- | :--- | :--- |
176
+ | `api_key` | `str` | **Required** | Your SolveCaptcha API key. |
177
+ | `base_url` | `str` | `"https://solvecaptcha.com"` | Custom API base URL. |
178
+ | `timeout` | `float` | `30.0` | HTTP request timeout in seconds. |
179
+ | `default_polling_timeout` | `float` | `120.0` | Max seconds to wait for resolution. |
180
+ | `default_polling_interval` | `float` | `5.0` | Seconds between status checks. |
181
+
182
+ #### Methods
183
+
184
+ - **`get_balance() -> float`** — Returns current balance in USD.
185
+ - **`solve_turnstile(sitekey, pageurl, action=None, data=None) -> str`** — Solves Cloudflare Turnstile.
186
+ - **`solve_recaptcha_v2(googlekey, pageurl, invisible=0, data_s=None, enterprise=0) -> str`** — Solves reCAPTCHA v2.
187
+ - **`solve_recaptcha_v3(googlekey, pageurl, action=None, min_score=None) -> str`** — Solves reCAPTCHA v3.
188
+ - **`solve_funcaptcha(publickey, pageurl, surl=None, data=None) -> str`** — Solves Arkose FunCaptcha.
189
+ - **`solve_geetest(gt, challenge, pageurl, api_server=None) -> GeeTestResult`** — Solves GeeTest v3.
190
+ - **`solve_image(body, phrase=0, regsense=0, numeric=0, calc=0, min_len=None, max_len=None, language=None, textinstructions=None) -> str`** — Solves base64 image.
191
+ - **`solve_text(text, language=None) -> str`** — Solves text question captcha.
192
+ - **`create_task(params: dict) -> str`** — Sends raw task payload to `/in.php` and returns task ID.
193
+ - **`get_task_result(task_id: str) -> str | None`** — Checks task status, returns token or `None` if not ready.
194
+ - **`wait_for_result(task_id: str, timeout_sec=None, interval_sec=None) -> str`** — Polls until solved.
195
+ - **`report_good(task_id: str) -> bool`** — Marks solution as correct.
196
+ - **`report_bad(task_id: str) -> bool`** — Marks solution as incorrect to receive a credit refund.
197
+
198
+ ---
199
+
200
+ ## Error Handling
201
+
202
+ When an API error occurs, `SolveCaptchaError` is raised:
203
+
204
+ ```python
205
+ from solvecaptcha import SolveCaptchaClient, SolveCaptchaError
206
+
207
+ try:
208
+ with SolveCaptchaClient(api_key="invalid_key") as client:
209
+ client.get_balance()
210
+ except SolveCaptchaError as exc:
211
+ print(f"Error Code: {exc.code}")
212
+ print(f"Message: {exc.message}")
213
+ ```
214
+
215
+ Common error codes:
216
+ - `ERROR_WRONG_USER_KEY` — Provided API key is incorrect.
217
+ - `ERROR_KEY_DOES_NOT_EXIST` — Invalid API key.
218
+ - `ERROR_ZERO_BALANCE` — Insufficient funds on account.
219
+ - `ERROR_NO_SLOT_AVAILABLE` — Solving queue currently congested.
220
+ - `CAPCHA_NOT_READY` — Resolution in progress.
221
+ - `TIMEOUT` — Maximum polling wait duration exceeded.
222
+
223
+ ---
224
+
225
+ ## License
226
+
227
+ ISC
@@ -0,0 +1,8 @@
1
+ solvecaptcha/__init__.py,sha256=tgVLTlnkBP5leBgX5iGlAGioSYxsNOcjq_4djncignA,304
2
+ solvecaptcha/client.py,sha256=lwV7afPPPlf8Y7Gd6Ew0s0zwaB2WhvpT6Ui0-ce82lY,16777
3
+ solvecaptcha/errors.py,sha256=Rj8HOcsZ2ji-P4k7x0sD_b3pwfWdVhSLqgiU7BKTGds,1487
4
+ solvecaptcha/types.py,sha256=Eyb85NxxMISk0IGIGK8UOYphL-TbNXcrPdSEtYpViTE,362
5
+ solvecaptcha-0.1.0.dist-info/METADATA,sha256=PhGqFOX3pPihwi20gFh2jvgnPZJW7pY4ZgaybEqgyQY,7400
6
+ solvecaptcha-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
7
+ solvecaptcha-0.1.0.dist-info/licenses/LICENSE,sha256=8S0uYmn_aeljwFudfBoV91HcIo79yab9s0yhMVsOHJ0,751
8
+ solvecaptcha-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 api-kit contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.