python3-capsolver 1.1.0__tar.gz → 1.2.0__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 (49) hide show
  1. {python3_capsolver-1.1.0/src/python3_capsolver.egg-info → python3_capsolver-1.2.0}/PKG-INFO +76 -3
  2. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/README.md +74 -1
  3. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/pyproject.toml +16 -1
  4. python3_capsolver-1.2.0/src/python3_capsolver/__version__.py +1 -0
  5. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/aio_captcha_instrument.py +9 -2
  6. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/captcha_instrument.py +4 -1
  7. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/serializer.py +10 -4
  8. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/sio_captcha_instrument.py +9 -1
  9. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/image_to_text.py +6 -1
  10. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0/src/python3_capsolver.egg-info}/PKG-INFO +76 -3
  11. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver.egg-info/requires.txt +1 -1
  12. python3_capsolver-1.2.0/tests/test_control.py +175 -0
  13. python3_capsolver-1.1.0/src/python3_capsolver/__version__.py +0 -1
  14. python3_capsolver-1.1.0/tests/test_control.py +0 -38
  15. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/LICENSE +0 -0
  16. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/MANIFEST.in +0 -0
  17. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/setup.cfg +0 -0
  18. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/__init__.py +0 -0
  19. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/aws_waf.py +0 -0
  20. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/cloudflare.py +0 -0
  21. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/control.py +0 -0
  22. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/__init__.py +0 -0
  23. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/base.py +0 -0
  24. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/const.py +0 -0
  25. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/context_instr.py +0 -0
  26. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/enum.py +0 -0
  27. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/core/utils.py +0 -0
  28. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/datadome_slider.py +0 -0
  29. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/friendly_captcha.py +0 -0
  30. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/gee_test.py +0 -0
  31. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/mt_captcha.py +0 -0
  32. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/recaptcha.py +0 -0
  33. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/vision_engine.py +0 -0
  34. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver/yandex.py +0 -0
  35. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver.egg-info/SOURCES.txt +0 -0
  36. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver.egg-info/dependency_links.txt +0 -0
  37. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/src/python3_capsolver.egg-info/top_level.txt +0 -0
  38. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_aws_waf.py +0 -0
  39. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_cloudflare.py +0 -0
  40. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_core.py +0 -0
  41. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_datadome.py +0 -0
  42. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_friendly.py +0 -0
  43. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_gee_test.py +0 -0
  44. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_image_to_text.py +0 -0
  45. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_instrument.py +0 -0
  46. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_mt_captcha.py +0 -0
  47. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_recaptcha.py +0 -0
  48. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_vision_engine.py +0 -0
  49. {python3_capsolver-1.1.0 → python3_capsolver-1.2.0}/tests/test_yandex.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-capsolver
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Python 3.8+ Capsolver library with AIO module.
5
5
  Author-email: AndreiDrang <python-captcha@pm.me>
6
6
  License: MIT License
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: requests>=2.21.0
32
32
  Requires-Dist: aiohttp>=3.9.2
33
- Requires-Dist: msgspec<0.20,>=0.18
33
+ Requires-Dist: msgspec<=0.21,>=0.18
34
34
  Requires-Dist: tenacity<10,>=8
35
35
  Dynamic: license-file
36
36
 
@@ -61,6 +61,13 @@ Tested on UNIX based OS.
61
61
 
62
62
  The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
63
63
 
64
+ ## Features
65
+ - **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
66
+ - **Type Safety**: Enums for captcha types and response statuses.
67
+ - **Resilience**: Built-in retries using `tenacity`.
68
+ - **Performance**: High-speed JSON serialization with `msgspec`.
69
+ - **Coverage**: Supports ReCaptcha (V2/V3), Cloudflare, DataDome, GeeTest, MtCaptcha, AWS WAF, Yandex, and ImageToText.
70
+
64
71
  ## How to install?
65
72
 
66
73
  We recommend using the latest version of Python. `python3-capsolver` supports Python 3.7+.
@@ -73,8 +80,74 @@ pip install python3-capsolver
73
80
 
74
81
  ## How to use?
75
82
 
76
- Is described in the [documentation-website](https://andreidrang.github.io/python3-capsolver/).
83
+ Detailed documentation is available on the [website](https://andreidrang.github.io/python3-capsolver/).
84
+
85
+ ### Quick Start
86
+
87
+ #### Synchronous Example (ImageToText)
88
+ ```python
89
+ from python3_capsolver.image_to_text import ImageToText
90
+
91
+ # 1. Initialize with API Key
92
+ solver = ImageToText(api_key="YOUR_API_KEY")
93
+
94
+ # 2. Solve
95
+ result = solver.captcha_handler(
96
+ task_payload={
97
+ "body": "base64_encoded_image_string"
98
+ }
99
+ )
100
+
101
+ # 3. Check result
102
+ if result["errorId"] == 0:
103
+ print("Solution:", result["solution"])
104
+ else:
105
+ print("Error:", result["errorCode"])
106
+ ```
107
+
108
+ #### Asynchronous Example (ReCaptcha)
109
+ ```python
110
+ import asyncio
111
+ from python3_capsolver.recaptcha import ReCaptcha
112
+ from python3_capsolver.core.enum import CaptchaTypeEnm
113
+
114
+ async def main():
115
+ # 1. Initialize
116
+ solver = ReCaptcha(
117
+ api_key="YOUR_API_KEY",
118
+ captcha_type=CaptchaTypeEnm.ReCaptchaV2TaskProxyLess
119
+ )
120
+
121
+ # 2. Solve
122
+ result = await solver.aio_captcha_handler(
123
+ task_payload={
124
+ "websiteURL": "https://example.com",
125
+ "websiteKey": "SITE_KEY"
126
+ }
127
+ )
128
+
129
+ print(result)
130
+
131
+ if __name__ == "__main__":
132
+ asyncio.run(main())
133
+ ```
77
134
 
135
+ ## Supported Captcha Types
136
+ - **ReCaptcha**: V2 (Task/Enterprise), V3 (Task/Enterprise)
137
+ - **HCaptcha**: Task, Enterprise
138
+ - **Cloudflare**: Turnstile
139
+ - **GeeTest**: V3, V4
140
+ - **DataDome**: Slider
141
+ - **MtCaptcha**
142
+ - **AWS WAF**
143
+ - **Yandex SmartCaptcha**
144
+ - **ImageToText**: General image CAPTCHAs
145
+
146
+ ## Documentation & Context (For LLMs)
147
+ - **Project Structure**: See `AGENTS.md` in root and subdirectories for internal architecture.
148
+ - **Entry Points**: `src/python3_capsolver/*.py` contains service-specific classes (e.g., `ReCaptcha`, `HCaptcha`).
149
+ - **Core Logic**: `src/python3_capsolver/core/base.py` handles the API communication loop.
150
+ - **Enums**: Use `python3_capsolver.core.enum` for type-safe parameters.
78
151
 
79
152
  ## How to test?
80
153
 
@@ -25,6 +25,13 @@ Tested on UNIX based OS.
25
25
 
26
26
  The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
27
27
 
28
+ ## Features
29
+ - **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
30
+ - **Type Safety**: Enums for captcha types and response statuses.
31
+ - **Resilience**: Built-in retries using `tenacity`.
32
+ - **Performance**: High-speed JSON serialization with `msgspec`.
33
+ - **Coverage**: Supports ReCaptcha (V2/V3), Cloudflare, DataDome, GeeTest, MtCaptcha, AWS WAF, Yandex, and ImageToText.
34
+
28
35
  ## How to install?
29
36
 
30
37
  We recommend using the latest version of Python. `python3-capsolver` supports Python 3.7+.
@@ -37,8 +44,74 @@ pip install python3-capsolver
37
44
 
38
45
  ## How to use?
39
46
 
40
- Is described in the [documentation-website](https://andreidrang.github.io/python3-capsolver/).
47
+ Detailed documentation is available on the [website](https://andreidrang.github.io/python3-capsolver/).
48
+
49
+ ### Quick Start
50
+
51
+ #### Synchronous Example (ImageToText)
52
+ ```python
53
+ from python3_capsolver.image_to_text import ImageToText
54
+
55
+ # 1. Initialize with API Key
56
+ solver = ImageToText(api_key="YOUR_API_KEY")
57
+
58
+ # 2. Solve
59
+ result = solver.captcha_handler(
60
+ task_payload={
61
+ "body": "base64_encoded_image_string"
62
+ }
63
+ )
64
+
65
+ # 3. Check result
66
+ if result["errorId"] == 0:
67
+ print("Solution:", result["solution"])
68
+ else:
69
+ print("Error:", result["errorCode"])
70
+ ```
71
+
72
+ #### Asynchronous Example (ReCaptcha)
73
+ ```python
74
+ import asyncio
75
+ from python3_capsolver.recaptcha import ReCaptcha
76
+ from python3_capsolver.core.enum import CaptchaTypeEnm
77
+
78
+ async def main():
79
+ # 1. Initialize
80
+ solver = ReCaptcha(
81
+ api_key="YOUR_API_KEY",
82
+ captcha_type=CaptchaTypeEnm.ReCaptchaV2TaskProxyLess
83
+ )
84
+
85
+ # 2. Solve
86
+ result = await solver.aio_captcha_handler(
87
+ task_payload={
88
+ "websiteURL": "https://example.com",
89
+ "websiteKey": "SITE_KEY"
90
+ }
91
+ )
92
+
93
+ print(result)
94
+
95
+ if __name__ == "__main__":
96
+ asyncio.run(main())
97
+ ```
41
98
 
99
+ ## Supported Captcha Types
100
+ - **ReCaptcha**: V2 (Task/Enterprise), V3 (Task/Enterprise)
101
+ - **HCaptcha**: Task, Enterprise
102
+ - **Cloudflare**: Turnstile
103
+ - **GeeTest**: V3, V4
104
+ - **DataDome**: Slider
105
+ - **MtCaptcha**
106
+ - **AWS WAF**
107
+ - **Yandex SmartCaptcha**
108
+ - **ImageToText**: General image CAPTCHAs
109
+
110
+ ## Documentation & Context (For LLMs)
111
+ - **Project Structure**: See `AGENTS.md` in root and subdirectories for internal architecture.
112
+ - **Entry Points**: `src/python3_capsolver/*.py` contains service-specific classes (e.g., `ReCaptcha`, `HCaptcha`).
113
+ - **Core Logic**: `src/python3_capsolver/core/base.py` handles the API communication loop.
114
+ - **Enums**: Use `python3_capsolver.core.enum` for type-safe parameters.
42
115
 
43
116
  ## How to test?
44
117
 
@@ -86,7 +86,7 @@ classifiers = [
86
86
  dependencies = [
87
87
  "requests>=2.21.0",
88
88
  "aiohttp>=3.9.2",
89
- "msgspec>=0.18,<0.20",
89
+ "msgspec>=0.18,<=0.21",
90
90
  "tenacity>=8,<10"
91
91
  ]
92
92
 
@@ -103,3 +103,18 @@ Documentation = "https://andreidrang.github.io/python3-capsolver/"
103
103
  Repository = "https://github.com/AndreiDrang/python3-capsolver"
104
104
  Issues = "https://github.com/AndreiDrang/python3-capsolver/issues"
105
105
  Changelog = "https://github.com/AndreiDrang/python3-capsolver/releases"
106
+
107
+ [dependency-groups]
108
+ dev = [
109
+ "autoflake>=2.3.1",
110
+ "black>=24.8.0",
111
+ "coverage>=7.6.1",
112
+ "isort>=5.13.2",
113
+ "pytest>=8.3.5",
114
+ "pytest-asyncio>=0.24.0",
115
+ ]
116
+ docs = [
117
+ "myst-parser>=3.0.1",
118
+ "pallets-sphinx-themes>=2.3.0",
119
+ "sphinx>=7.1.2",
120
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "1.2.0"
@@ -30,6 +30,8 @@ class AIOCaptchaInstrument(CaptchaInstrumentBase):
30
30
 
31
31
  # if task created and already ready - return result
32
32
  if self.created_task_data.errorId == 0:
33
+ if str(self.created_task_data.status).lower() == ResponseStatusEnm.Ready.value:
34
+ return self.created_task_data.to_dict()
33
35
  return (await self.__get_result()).to_dict()
34
36
  else:
35
37
  self.created_task_data.status = ResponseStatusEnm.Failed
@@ -72,7 +74,10 @@ class AIOCaptchaInstrument(CaptchaInstrumentBase):
72
74
  ) as resp:
73
75
  if resp.status in VALID_STATUS_CODES:
74
76
  result_data = CaptchaResponseSer(**await resp.json())
75
- if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
77
+ if result_data.status in (
78
+ ResponseStatusEnm.Ready,
79
+ ResponseStatusEnm.Failed,
80
+ ):
76
81
  # if captcha ready\failed or have unknown status - return exist data
77
82
  return result_data
78
83
  else:
@@ -90,10 +95,12 @@ class AIOCaptchaInstrument(CaptchaInstrumentBase):
90
95
  self.result.errorDescription = self.CAPTCHA_UNSOLVABLE_DESCRIPTION
91
96
  self.result.taskId = self.created_task_data.taskId
92
97
  self.result.status = ResponseStatusEnm.Failed
98
+ return self.result
93
99
 
94
100
  @staticmethod
95
101
  async def send_post_request(
96
- payload: Optional[dict] = None, url_postfix: EndpointPostfixEnm = EndpointPostfixEnm.GET_BALANCE
102
+ payload: Optional[dict] = None,
103
+ url_postfix: EndpointPostfixEnm = EndpointPostfixEnm.GET_BALANCE,
97
104
  ) -> dict:
98
105
  """
99
106
  Function send ASYNC request to service and wait for result
@@ -83,7 +83,10 @@ class FileInstrument:
83
83
  async for attempt in ASYNC_RETRIES:
84
84
  with attempt:
85
85
  async with session.get(url=url, **kwargs) as resp:
86
- return await resp.content.read()
86
+ if resp.status == 200:
87
+ return await resp.content.read()
88
+ return b""
89
+ return b""
87
90
 
88
91
  def file_processing(
89
92
  self,
@@ -1,12 +1,18 @@
1
1
  from enum import Enum
2
- from typing import Any, Dict, Literal, Optional
2
+ from typing import Any, Dict, Union, Optional
3
3
 
4
4
  from msgspec import Struct
5
5
 
6
6
  from .enum import ResponseStatusEnm
7
7
  from .const import APP_ID
8
8
 
9
- __all__ = ("PostRequestSer", "TaskSer", "RequestCreateTaskSer", "CaptchaResponseSer", "RequestGetTaskResultSer")
9
+ __all__ = (
10
+ "PostRequestSer",
11
+ "TaskSer",
12
+ "RequestCreateTaskSer",
13
+ "CaptchaResponseSer",
14
+ "RequestGetTaskResultSer",
15
+ )
10
16
 
11
17
 
12
18
  class MyBaseModel(Struct):
@@ -36,7 +42,7 @@ class TaskSer(MyBaseModel):
36
42
 
37
43
 
38
44
  class RequestCreateTaskSer(PostRequestSer):
39
- appId: Literal[APP_ID] = APP_ID
45
+ appId: str = APP_ID
40
46
 
41
47
 
42
48
  class RequestGetTaskResultSer(MyBaseModel):
@@ -58,5 +64,5 @@ class ResponseSer(MyBaseModel):
58
64
 
59
65
  class CaptchaResponseSer(ResponseSer):
60
66
  taskId: Optional[str] = None
61
- status: ResponseStatusEnm = ResponseStatusEnm.Processing
67
+ status: Union[ResponseStatusEnm, str] = ResponseStatusEnm.Processing
62
68
  solution: Optional[Dict[str, Any]] = None
@@ -24,6 +24,7 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
24
24
  super().__init__()
25
25
  self.captcha_params = captcha_params
26
26
  self.created_task_data = CaptchaResponseSer
27
+
27
28
  # prepare session
28
29
  self.session = requests.Session()
29
30
  self.session.mount("http://", HTTPAdapter(max_retries=RETRIES))
@@ -37,6 +38,8 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
37
38
 
38
39
  # if task created and ready - return result
39
40
  if self.created_task_data.errorId == 0:
41
+ if str(self.created_task_data.status).lower() == ResponseStatusEnm.Ready.value:
42
+ return self.created_task_data.to_dict()
40
43
  return self.__get_result().to_dict()
41
44
  else:
42
45
  self.created_task_data.status = ResponseStatusEnm.Failed
@@ -78,7 +81,10 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
78
81
  )
79
82
  if resp.status_code in VALID_STATUS_CODES:
80
83
  result_data = CaptchaResponseSer(**resp.json())
81
- if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
84
+ if result_data.status in (
85
+ ResponseStatusEnm.Ready,
86
+ ResponseStatusEnm.Failed,
87
+ ):
82
88
  # if captcha ready\failed or have unknown status - return exist data
83
89
  return result_data
84
90
  else:
@@ -89,12 +95,14 @@ class SIOCaptchaInstrument(CaptchaInstrumentBase):
89
95
 
90
96
  # if captcha just created or in processing now - wait
91
97
  time.sleep(self.captcha_params.sleep_time)
98
+
92
99
  # default response if server is silent
93
100
  self.result.errorId = 1
94
101
  self.result.errorCode = self.CAPTCHA_UNSOLVABLE
95
102
  self.result.errorDescription = self.CAPTCHA_UNSOLVABLE_DESCRIPTION
96
103
  self.result.taskId = self.created_task_data.taskId
97
104
  self.result.status = ResponseStatusEnm.Failed
105
+ return self.result
98
106
 
99
107
  @staticmethod
100
108
  def send_post_request(
@@ -83,7 +83,12 @@ class ImageToText(CaptchaParams):
83
83
  """
84
84
 
85
85
  def __init__(
86
- self, api_key: str, captcha_type: Union[CaptchaTypeEnm, str] = CaptchaTypeEnm.ImageToTextTask, **kwargs
86
+ self,
87
+ api_key: str,
88
+ captcha_type: Union[CaptchaTypeEnm, str] = CaptchaTypeEnm.ImageToTextTask,
89
+ **kwargs,
87
90
  ):
91
+ if isinstance(captcha_type, str):
92
+ captcha_type = CaptchaTypeEnm(captcha_type)
88
93
 
89
94
  super().__init__(api_key=api_key, captcha_type=captcha_type, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-capsolver
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Python 3.8+ Capsolver library with AIO module.
5
5
  Author-email: AndreiDrang <python-captcha@pm.me>
6
6
  License: MIT License
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: requests>=2.21.0
32
32
  Requires-Dist: aiohttp>=3.9.2
33
- Requires-Dist: msgspec<0.20,>=0.18
33
+ Requires-Dist: msgspec<=0.21,>=0.18
34
34
  Requires-Dist: tenacity<10,>=8
35
35
  Dynamic: license-file
36
36
 
@@ -61,6 +61,13 @@ Tested on UNIX based OS.
61
61
 
62
62
  The library is intended for software developers and is used to work with the [Capsolver](https://dashboard.capsolver.com/passport/register?inviteCode=kQTn-tG07Jb1) service API.
63
63
 
64
+ ## Features
65
+ - **Sync & Async Support**: Full support for both synchronous (`requests`) and asynchronous (`aiohttp`) operations.
66
+ - **Type Safety**: Enums for captcha types and response statuses.
67
+ - **Resilience**: Built-in retries using `tenacity`.
68
+ - **Performance**: High-speed JSON serialization with `msgspec`.
69
+ - **Coverage**: Supports ReCaptcha (V2/V3), Cloudflare, DataDome, GeeTest, MtCaptcha, AWS WAF, Yandex, and ImageToText.
70
+
64
71
  ## How to install?
65
72
 
66
73
  We recommend using the latest version of Python. `python3-capsolver` supports Python 3.7+.
@@ -73,8 +80,74 @@ pip install python3-capsolver
73
80
 
74
81
  ## How to use?
75
82
 
76
- Is described in the [documentation-website](https://andreidrang.github.io/python3-capsolver/).
83
+ Detailed documentation is available on the [website](https://andreidrang.github.io/python3-capsolver/).
84
+
85
+ ### Quick Start
86
+
87
+ #### Synchronous Example (ImageToText)
88
+ ```python
89
+ from python3_capsolver.image_to_text import ImageToText
90
+
91
+ # 1. Initialize with API Key
92
+ solver = ImageToText(api_key="YOUR_API_KEY")
93
+
94
+ # 2. Solve
95
+ result = solver.captcha_handler(
96
+ task_payload={
97
+ "body": "base64_encoded_image_string"
98
+ }
99
+ )
100
+
101
+ # 3. Check result
102
+ if result["errorId"] == 0:
103
+ print("Solution:", result["solution"])
104
+ else:
105
+ print("Error:", result["errorCode"])
106
+ ```
107
+
108
+ #### Asynchronous Example (ReCaptcha)
109
+ ```python
110
+ import asyncio
111
+ from python3_capsolver.recaptcha import ReCaptcha
112
+ from python3_capsolver.core.enum import CaptchaTypeEnm
113
+
114
+ async def main():
115
+ # 1. Initialize
116
+ solver = ReCaptcha(
117
+ api_key="YOUR_API_KEY",
118
+ captcha_type=CaptchaTypeEnm.ReCaptchaV2TaskProxyLess
119
+ )
120
+
121
+ # 2. Solve
122
+ result = await solver.aio_captcha_handler(
123
+ task_payload={
124
+ "websiteURL": "https://example.com",
125
+ "websiteKey": "SITE_KEY"
126
+ }
127
+ )
128
+
129
+ print(result)
130
+
131
+ if __name__ == "__main__":
132
+ asyncio.run(main())
133
+ ```
77
134
 
135
+ ## Supported Captcha Types
136
+ - **ReCaptcha**: V2 (Task/Enterprise), V3 (Task/Enterprise)
137
+ - **HCaptcha**: Task, Enterprise
138
+ - **Cloudflare**: Turnstile
139
+ - **GeeTest**: V3, V4
140
+ - **DataDome**: Slider
141
+ - **MtCaptcha**
142
+ - **AWS WAF**
143
+ - **Yandex SmartCaptcha**
144
+ - **ImageToText**: General image CAPTCHAs
145
+
146
+ ## Documentation & Context (For LLMs)
147
+ - **Project Structure**: See `AGENTS.md` in root and subdirectories for internal architecture.
148
+ - **Entry Points**: `src/python3_capsolver/*.py` contains service-specific classes (e.g., `ReCaptcha`, `HCaptcha`).
149
+ - **Core Logic**: `src/python3_capsolver/core/base.py` handles the API communication loop.
150
+ - **Enums**: Use `python3_capsolver.core.enum` for type-safe parameters.
78
151
 
79
152
  ## How to test?
80
153
 
@@ -1,4 +1,4 @@
1
1
  requests>=2.21.0
2
2
  aiohttp>=3.9.2
3
- msgspec<0.20,>=0.18
3
+ msgspec<=0.21,>=0.18
4
4
  tenacity<10,>=8
@@ -0,0 +1,175 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock, AsyncMock
3
+
4
+ from tests.conftest import BaseTest
5
+ from python3_capsolver.control import Control
6
+
7
+
8
+ class TestControl(BaseTest):
9
+ """
10
+ Success tests
11
+ """
12
+
13
+ def test_get_balance_exist(self):
14
+ assert "get_balance" in Control.__dict__.keys()
15
+ assert "aio_get_balance" in Control.__dict__.keys()
16
+
17
+ def test_get_balance(self):
18
+ result = Control(api_key=self.API_KEY).get_balance()
19
+ assert isinstance(result, dict)
20
+ assert result["errorId"] == 0
21
+ assert result["balance"] != 0.0
22
+
23
+ async def test_aio_get_balance(self):
24
+ result = await Control(api_key=self.API_KEY).aio_get_balance()
25
+ assert isinstance(result, dict)
26
+ assert result["errorId"] == 0
27
+ assert result["balance"] != 0.0
28
+
29
+ """
30
+ Failed tests
31
+ """
32
+
33
+ def test_get_balance_api_key_err(self):
34
+ with pytest.raises(ValueError):
35
+ Control(api_key=self.get_random_string(36)).get_balance()
36
+
37
+ async def test_aio_get_balance_api_key_err(self):
38
+ with pytest.raises(ValueError):
39
+ await Control(api_key=self.get_random_string(36)).aio_get_balance()
40
+
41
+
42
+ class TestControlMock(BaseTest):
43
+ """
44
+ Mocked Unit tests for Control class
45
+ """
46
+
47
+ @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
48
+ def test_create_task(self, mock_post):
49
+ mock_response = MagicMock()
50
+ mock_response.status_code = 200
51
+ mock_response.json.return_value = {"errorId": 0, "taskId": "test-task-id"}
52
+ mock_post.return_value = mock_response
53
+
54
+ control = Control(api_key="test-key")
55
+ result = control.create_task({"type": "ImageToTextTask", "body": "base64..."})
56
+
57
+ assert result["taskId"] == "test-task-id"
58
+ assert result["errorId"] == 0
59
+ mock_post.assert_called_once()
60
+
61
+ @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
62
+ async def test_aio_create_task(self, mock_post):
63
+ mock_resp = MagicMock()
64
+ mock_resp.status = 200
65
+ mock_resp.json = AsyncMock(
66
+ return_value={"errorId": 0, "taskId": "test-task-id"}
67
+ )
68
+ # Mock async context manager
69
+ mock_resp.__aenter__.return_value = mock_resp
70
+ mock_post.return_value = mock_resp
71
+
72
+ control = Control(api_key="test-key")
73
+ result = await control.aio_create_task(
74
+ {"type": "ImageToTextTask", "body": "base64..."}
75
+ )
76
+
77
+ assert result["taskId"] == "test-task-id"
78
+ assert result["errorId"] == 0
79
+
80
+ @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
81
+ def test_get_task_result(self, mock_post):
82
+ mock_response = MagicMock()
83
+ mock_response.status_code = 200
84
+ mock_response.json.return_value = {
85
+ "errorId": 0,
86
+ "status": "ready",
87
+ "solution": {"text": "abc"},
88
+ }
89
+ mock_post.return_value = mock_response
90
+
91
+ control = Control(api_key="test-key")
92
+ result = control.get_task_result(task_id="test-id")
93
+
94
+ assert result["status"] == "ready"
95
+ assert result["solution"]["text"] == "abc"
96
+
97
+ @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
98
+ async def test_aio_get_task_result(self, mock_post):
99
+ mock_resp = MagicMock()
100
+ mock_resp.status = 200
101
+ mock_resp.json = AsyncMock(
102
+ return_value={
103
+ "errorId": 0,
104
+ "status": "ready",
105
+ "solution": {"text": "abc"},
106
+ }
107
+ )
108
+ mock_resp.__aenter__.return_value = mock_resp
109
+ mock_post.return_value = mock_resp
110
+
111
+ control = Control(api_key="test-key")
112
+ result = await control.aio_get_task_result(task_id="test-id")
113
+
114
+ assert result["status"] == "ready"
115
+ assert result["solution"]["text"] == "abc"
116
+
117
+ @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
118
+ def test_get_token(self, mock_post):
119
+ mock_response = MagicMock()
120
+ mock_response.status_code = 200
121
+ mock_response.json.return_value = {"errorId": 0, "taskId": "token-task-id"}
122
+ mock_post.return_value = mock_response
123
+
124
+ control = Control(api_key="test-key")
125
+ result = control.get_token(
126
+ {"type": "ReCaptchaV3TaskProxyLess", "websiteURL": "..."}
127
+ )
128
+
129
+ assert result["taskId"] == "token-task-id"
130
+
131
+ @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
132
+ async def test_aio_get_token(self, mock_post):
133
+ mock_resp = MagicMock()
134
+ mock_resp.status = 200
135
+ mock_resp.json = AsyncMock(
136
+ return_value={"errorId": 0, "taskId": "token-task-id"}
137
+ )
138
+ mock_resp.__aenter__.return_value = mock_resp
139
+ mock_post.return_value = mock_resp
140
+
141
+ control = Control(api_key="test-key")
142
+ result = await control.aio_get_token(
143
+ {"type": "ReCaptchaV3TaskProxyLess", "websiteURL": "..."}
144
+ )
145
+
146
+ assert result["taskId"] == "token-task-id"
147
+
148
+ @patch("python3_capsolver.core.sio_captcha_instrument.requests.Session.post")
149
+ def test_feedback_task(self, mock_post):
150
+ mock_response = MagicMock()
151
+ mock_response.status_code = 200
152
+ mock_response.json.return_value = {"errorId": 0, "message": "okay"}
153
+ mock_post.return_value = mock_response
154
+
155
+ control = Control(api_key="test-key")
156
+ result = control.feedback_task(
157
+ task_id="test-id", result_payload={"invalid": True}
158
+ )
159
+
160
+ assert result["message"] == "okay"
161
+
162
+ @patch("python3_capsolver.core.aio_captcha_instrument.aiohttp.ClientSession.post")
163
+ async def test_aio_feedback_task(self, mock_post):
164
+ mock_resp = MagicMock()
165
+ mock_resp.status = 200
166
+ mock_resp.json = AsyncMock(return_value={"errorId": 0, "message": "okay"})
167
+ mock_resp.__aenter__.return_value = mock_resp
168
+ mock_post.return_value = mock_resp
169
+
170
+ control = Control(api_key="test-key")
171
+ result = await control.aio_feedback_task(
172
+ task_id="test-id", result_payload={"invalid": True}
173
+ )
174
+
175
+ assert result["message"] == "okay"
@@ -1 +0,0 @@
1
- __version__ = "1.1.0"
@@ -1,38 +0,0 @@
1
- import pytest
2
-
3
- from tests.conftest import BaseTest
4
- from python3_capsolver.control import Control
5
-
6
-
7
- class TestControl(BaseTest):
8
- """
9
- Success tests
10
- """
11
-
12
- def test_get_balance_exist(self):
13
- assert "get_balance" in Control.__dict__.keys()
14
- assert "aio_get_balance" in Control.__dict__.keys()
15
-
16
- def test_get_balance(self):
17
- result = Control(api_key=self.API_KEY).get_balance()
18
- assert isinstance(result, dict)
19
- assert result["errorId"] == 0
20
- assert result["balance"] != 0.0
21
-
22
- async def test_aio_get_balance(self):
23
- result = await Control(api_key=self.API_KEY).aio_get_balance()
24
- assert isinstance(result, dict)
25
- assert result["errorId"] == 0
26
- assert result["balance"] != 0.0
27
-
28
- """
29
- Failed tests
30
- """
31
-
32
- def test_get_balance_api_key_err(self):
33
- with pytest.raises(ValueError):
34
- Control(api_key=self.get_random_string(36)).get_balance()
35
-
36
- async def test_aio_get_balance_api_key_err(self):
37
- with pytest.raises(ValueError):
38
- await Control(api_key=self.get_random_string(36)).aio_get_balance()