python-rucaptcha 6.3.2__tar.gz → 6.5.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 (76) hide show
  1. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/LICENSE +1 -1
  2. {python_rucaptcha-6.3.2/src/python_rucaptcha.egg-info → python_rucaptcha-6.5.0}/PKG-INFO +6 -6
  3. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/pyproject.toml +5 -3
  4. python_rucaptcha-6.5.0/src/python_rucaptcha/__version__.py +1 -0
  5. python_rucaptcha-6.5.0/src/python_rucaptcha/captcha_fox.py +124 -0
  6. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/base.py +49 -27
  7. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/config.py +3 -1
  8. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/enums.py +12 -0
  9. python_rucaptcha-6.5.0/src/python_rucaptcha/core/result_handler.py +116 -0
  10. python_rucaptcha-6.5.0/src/python_rucaptcha/core/serializer.py +98 -0
  11. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/datadome_captcha.py +2 -2
  12. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/image_captcha.py +5 -5
  13. python_rucaptcha-6.5.0/src/python_rucaptcha/temu_captcha.py +152 -0
  14. python_rucaptcha-6.5.0/src/python_rucaptcha/vk_captcha.py +120 -0
  15. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0/src/python_rucaptcha.egg-info}/PKG-INFO +6 -6
  16. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/SOURCES.txt +3 -0
  17. python_rucaptcha-6.3.2/src/python_rucaptcha/__version__.py +0 -1
  18. python_rucaptcha-6.3.2/src/python_rucaptcha/core/result_handler.py +0 -69
  19. python_rucaptcha-6.3.2/src/python_rucaptcha/core/serializer.py +0 -74
  20. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/MANIFEST.in +0 -0
  21. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/README.md +0 -0
  22. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/setup.cfg +0 -0
  23. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/__init__.py +0 -0
  24. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/amazon_waf.py +0 -0
  25. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/atb_captcha.py +0 -0
  26. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/audio_captcha.py +0 -0
  27. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/bounding_box_captcha.py +0 -0
  28. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/capy_puzzle.py +0 -0
  29. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/control.py +0 -0
  30. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/coordinates_captcha.py +0 -0
  31. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/__init__.py +0 -0
  32. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/cutcaptcha.py +0 -0
  33. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/cyber_siara_captcha.py +0 -0
  34. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/draw_around_captcha.py +0 -0
  35. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/friendly_captcha.py +0 -0
  36. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/fun_captcha.py +0 -0
  37. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/gee_test.py +0 -0
  38. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/grid_captcha.py +0 -0
  39. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/hcaptcha.py +0 -0
  40. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/key_captcha.py +0 -0
  41. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/lemin_captcha.py +0 -0
  42. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/mt_captcha.py +0 -0
  43. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/prosopo.py +0 -0
  44. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/re_captcha.py +0 -0
  45. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/rotate_captcha.py +0 -0
  46. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/tencent.py +0 -0
  47. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/text_captcha.py +0 -0
  48. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/turnstile.py +0 -0
  49. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/dependency_links.txt +0 -0
  50. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/requires.txt +0 -0
  51. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/top_level.txt +0 -0
  52. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_amazon.py +0 -0
  53. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_audio.py +0 -0
  54. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_bounding_box.py +0 -0
  55. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_capypuzzle.py +0 -0
  56. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_control.py +0 -0
  57. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_coordinates.py +0 -0
  58. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_core.py +0 -0
  59. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_cutcaptcha.py +0 -0
  60. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_cybersiara.py +0 -0
  61. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_datadome.py +0 -0
  62. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_draw_around.py +0 -0
  63. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_friendly_captcha.py +0 -0
  64. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_funcaptcha.py +0 -0
  65. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_geetest.py +0 -0
  66. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_grid.py +0 -0
  67. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_hcaptcha.py +0 -0
  68. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_image.py +0 -0
  69. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_key_captcha.py +0 -0
  70. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_lemin.py +0 -0
  71. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_mtcaptcha.py +0 -0
  72. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_recaptcha.py +0 -0
  73. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_rotate.py +0 -0
  74. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_tencent.py +0 -0
  75. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_text.py +0 -0
  76. {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_turnstile.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Andrei
3
+ Copyright (c) 2025 Andrei
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,17 +1,15 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: python-rucaptcha
3
- Version: 6.3.2
3
+ Version: 6.5.0
4
4
  Summary: Python 3.9+ RuCaptcha library with AIO module.
5
5
  Author-email: AndreiDrang <python-captcha@pm.me>
6
- License: MIT License
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://andreidrang.github.io/python-rucaptcha/
8
8
  Project-URL: Documentation, https://andreidrang.github.io/python-rucaptcha/
9
9
  Project-URL: Repository, https://github.com/AndreiDrang/python-rucaptcha
10
10
  Project-URL: Issues, https://github.com/AndreiDrang/python-rucaptcha/issues
11
11
  Project-URL: Changelog, https://github.com/AndreiDrang/python-rucaptcha/releases
12
- Keywords: captcha,rucaptcha,2captcha,deathbycaptcha,recaptcha,geetest,hcaptcha,capypuzzle,rotatecaptcha,funcaptcha,keycaptcha,python3,recaptcha,captcha,security,tencent,atb_captcha,python-library,python-rucaptcha,rucaptcha-client,yandex,turnstile,amazon,amazon_waf,friendly-captcha
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
12
+ Keywords: captcha,rucaptcha,2captcha,deathbycaptcha,recaptcha,geetest,hcaptcha,capypuzzle,rotatecaptcha,funcaptcha,keycaptcha,python3,recaptcha,captcha,security,tencent,atb_captcha,python-library,python-rucaptcha,rucaptcha-client,yandex,turnstile,amazon,amazon_waf,vk-captcha,fox-captcha,temu-captcha,friendly-captcha
15
13
  Classifier: Development Status :: 5 - Production/Stable
16
14
  Classifier: Programming Language :: Python
17
15
  Classifier: Programming Language :: Python :: 3
@@ -20,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.9
20
18
  Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
23
22
  Classifier: Framework :: AsyncIO
24
23
  Classifier: Operating System :: Unix
25
24
  Classifier: Operating System :: Microsoft :: Windows
@@ -31,6 +30,7 @@ Requires-Dist: requests>=2.21.0
31
30
  Requires-Dist: aiohttp>=3.9.2
32
31
  Requires-Dist: msgspec<0.20,>=0.18
33
32
  Requires-Dist: tenacity<10,>=8
33
+ Dynamic: license-file
34
34
 
35
35
  # python-rucaptcha
36
36
 
@@ -66,12 +66,13 @@ keywords = [ "captcha",
66
66
  "turnstile",
67
67
  "amazon",
68
68
  "amazon_waf",
69
+ "vk-captcha",
70
+ "fox-captcha",
71
+ "temu-captcha",
69
72
  "friendly-captcha"
70
73
  ]
71
- license = {text = "MIT License"}
74
+ license = "MIT"
72
75
  classifiers = [
73
- "License :: OSI Approved :: MIT License",
74
- "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
75
76
  "Development Status :: 5 - Production/Stable",
76
77
  "Programming Language :: Python",
77
78
  "Programming Language :: Python :: 3",
@@ -80,6 +81,7 @@ classifiers = [
80
81
  "Programming Language :: Python :: 3.10",
81
82
  "Programming Language :: Python :: 3.11",
82
83
  "Programming Language :: Python :: 3.12",
84
+ "Programming Language :: Python :: 3.13",
83
85
  "Framework :: AsyncIO",
84
86
  "Operating System :: Unix",
85
87
  "Operating System :: Microsoft :: Windows",
@@ -0,0 +1 @@
1
+ __version__ = "6.5.0"
@@ -0,0 +1,124 @@
1
+ from .core.base import BaseCaptcha
2
+ from .core.enums import CaptchaFoxEnm
3
+
4
+
5
+ class CaptchaFox(BaseCaptcha):
6
+ def __init__(
7
+ self,
8
+ websiteURL: str,
9
+ websiteKey: str,
10
+ userAgent: str,
11
+ proxyType: str,
12
+ proxyAddress: str,
13
+ proxyPort: str,
14
+ *args,
15
+ **kwargs,
16
+ ):
17
+ """
18
+ The class is used to work with CaptchaFox.
19
+
20
+ Args:
21
+ rucaptcha_key: User API key
22
+ websiteURL: Full URL of the captcha page
23
+ websiteKey: The value of the `key` parameter.
24
+ It can be found in the page source code or captured in network requests during page loading.
25
+ userAgent: User-Agent of your browser will be used to load the captcha.
26
+ Use only modern browser's User-Agents
27
+ proxyType: Proxy type - `http`, `socks4`, `socks5`
28
+ proxyAddress: Proxy IP address or hostname
29
+ proxyPort: Proxy port
30
+ method: Captcha type
31
+ kwargs: Not required params for task creation request
32
+
33
+ Examples:
34
+ >>> CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122",
35
+ ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651",
36
+ ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
37
+ ... userAgent="Mozilla/5.0 .....",
38
+ ... proxyType="socks5",
39
+ ... proxyAddress="1.2.3.4",
40
+ ... proxyPort="445",
41
+ ... ).captcha_handler()
42
+ {
43
+ "errorId":0,
44
+ "status":"ready",
45
+ "solution":{
46
+ "token":"142000f.....er"
47
+ },
48
+ "cost":"0.002",
49
+ "ip":"1.2.3.4",
50
+ "createTime":1692863536,
51
+ "endTime":1692863556,
52
+ "solveCount":0,
53
+ "taskId": 73243152973,
54
+ }
55
+
56
+ >>> await CaptchaFox(rucaptcha_key="aa9011f31111181111168611f1151122",
57
+ ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651",
58
+ ... websiteKey="sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
59
+ ... userAgent="Mozilla/5.0 .....",
60
+ ... proxyType="socks5",
61
+ ... proxyAddress="1.2.3.4",
62
+ ... proxyPort="445",
63
+ ... ).aio_captcha_handler()
64
+ {
65
+ "errorId":0,
66
+ "status":"ready",
67
+ "solution":{
68
+ "token":"142000f.....er"
69
+ },
70
+ "cost":"0.002",
71
+ "ip":"1.2.3.4",
72
+ "createTime":1692863536,
73
+ "endTime":1692863556,
74
+ "solveCount":0,
75
+ "taskId": 73243152973,
76
+ }
77
+
78
+ Returns:
79
+ Dict with full server response
80
+
81
+ Notes:
82
+ https://2captcha.com/api-docs/captchafox
83
+
84
+ https://rucaptcha.com/api-docs/captchafox
85
+ """
86
+ super().__init__(method=CaptchaFoxEnm.CaptchaFoxTask, *args, **kwargs)
87
+
88
+ self.create_task_payload["task"].update(
89
+ {
90
+ "websiteURL": websiteURL,
91
+ "websiteKey": websiteKey,
92
+ "userAgent": userAgent,
93
+ "proxyType": proxyType,
94
+ "proxyAddress": proxyAddress,
95
+ "proxyPort": proxyPort,
96
+ }
97
+ )
98
+
99
+ def captcha_handler(self, **kwargs) -> dict:
100
+ """
101
+ Sync solving method
102
+
103
+ Args:
104
+ kwargs: additional params for `requests` library
105
+
106
+ Returns:
107
+ Dict with full server response
108
+
109
+ Notes:
110
+ Check class docstirng for more info
111
+ """
112
+ return self._processing_response(**kwargs)
113
+
114
+ async def aio_captcha_handler(self) -> dict:
115
+ """
116
+ Async solving method
117
+
118
+ Returns:
119
+ Dict with full server response
120
+
121
+ Notes:
122
+ Check class docstirng for more info
123
+ """
124
+ return await self._aio_processing_response()
@@ -3,7 +3,7 @@ import time
3
3
  import uuid
4
4
  import base64
5
5
  import asyncio
6
- from typing import Optional
6
+ from typing import Any, Optional
7
7
  from pathlib import Path
8
8
 
9
9
  import aiohttp
@@ -30,16 +30,32 @@ class BaseCaptcha:
30
30
  rucaptcha_key: str,
31
31
  method: str,
32
32
  sleep_time: int = 10,
33
- service_type: str = ServiceEnm.TWOCAPTCHA.value,
34
- **kwargs,
33
+ service_type: ServiceEnm | str = ServiceEnm.TWOCAPTCHA,
34
+ **kwargs: dict[str, Any],
35
35
  ):
36
36
  """
37
- :param rucaptcha_key: User API key
38
- :param method: Captcha type
39
- :param sleep_time: Time to wait for captcha solution
40
- :param service_type: URL with which the program will work, "2captcha" option is possible (standard)
41
- and "rucaptcha"
42
- :param kwargs: Designed to pass OPTIONAL parameters to the payload for a request to RuCaptcha
37
+ Base class for interacting with CAPTCHA-solving services such as 2Captcha and RuCaptcha.
38
+
39
+ This class handles the setup of request payloads, session configuration, and service-specific
40
+ parameters required to submit CAPTCHA tasks and retrieve their results. It supports optional
41
+ customization of task parameters via keyword arguments and includes retry logic for HTTP requests.
42
+
43
+ Args:
44
+ rucaptcha_key (str):
45
+ API key provided by the CAPTCHA-solving service.
46
+ method (str):
47
+ Type of CAPTCHA to solve (e.g., "ImageToText", "ReCaptchaV2").
48
+ sleep_time (int, optional):
49
+ Time in seconds to wait between polling attempts. Defaults to 10.
50
+ service_type (ServiceEnm | str, optional):
51
+ Service provider to use. Accepts `ServiceEnm.TWOCAPTCHA` or `"rucaptcha"`. Defaults to TWOCAPTCHA.
52
+ **kwargs (dict[str, Any]):
53
+ Optional parameters to be injected into the task payload (e.g., `websiteURL`, `siteKey`, `proxy`).
54
+
55
+ Example:
56
+ >>> captcha = BaseCaptcha("your-api-key", method="ReCaptchaV2", websiteURL="https://example.com", siteKey="abc123")
57
+ >>> captcha.create_task_payload
58
+ {'clientKey': 'your-api-key', 'task': {'type': 'ReCaptchaV2', 'websiteURL': 'https://example.com', 'siteKey': 'abc123'}}
43
59
  """
44
60
  self.result = GetTaskResultResponseSer()
45
61
  # assign args to validator
@@ -48,7 +64,7 @@ class BaseCaptcha:
48
64
 
49
65
  # prepare create task payload
50
66
  self.create_task_payload = CreateTaskBaseSer(
51
- clientKey=rucaptcha_key, task=TaskSer(type=method).to_dict()
67
+ clientKey=rucaptcha_key, task=TaskSer(type=method)
52
68
  ).to_dict()
53
69
  # prepare get task result data payload
54
70
  self.get_task_payload = GetTaskResultRequestSer(clientKey=rucaptcha_key)
@@ -61,7 +77,7 @@ class BaseCaptcha:
61
77
  self.session.mount("http://", HTTPAdapter(max_retries=RETRIES))
62
78
  self.session.mount("https://", HTTPAdapter(max_retries=RETRIES))
63
79
 
64
- def _processing_response(self, **kwargs: dict) -> dict:
80
+ def _processing_response(self, **kwargs: dict[str, Any]) -> dict[str, Any]:
65
81
  """
66
82
  Method processing captcha solving task creation result
67
83
  :param kwargs: additional params for Requests library
@@ -90,13 +106,13 @@ class BaseCaptcha:
90
106
  url_response=self.params.url_response,
91
107
  )
92
108
 
93
- def url_open(self, url: str, **kwargs):
109
+ def url_open(self, url: str, **kwargs: dict[str, Any]):
94
110
  """
95
111
  Method open links
96
112
  """
97
113
  return self.session.get(url=url, **kwargs)
98
114
 
99
- async def aio_url_read(self, url: str, **kwargs) -> bytes:
115
+ async def aio_url_read(self, url: str, **kwargs: dict[str, Any]) -> bytes | None:
100
116
  """
101
117
  Async method read bytes from link
102
118
  """
@@ -106,7 +122,7 @@ class BaseCaptcha:
106
122
  async with session.get(url=url, **kwargs) as resp:
107
123
  return await resp.content.read()
108
124
 
109
- async def _aio_processing_response(self) -> dict:
125
+ async def _aio_processing_response(self) -> dict[str, Any]:
110
126
  """
111
127
  Method processing async captcha solving task creation result
112
128
  """
@@ -167,23 +183,24 @@ class BaseCaptcha:
167
183
 
168
184
  def _body_file_processing(
169
185
  self,
170
- save_format: SaveFormatsEnm,
186
+ save_format: SaveFormatsEnm | str,
171
187
  file_path: str,
172
188
  file_extension: str = "png",
173
- captcha_link: Optional[str] = None,
174
- captcha_file: Optional[str] = None,
175
- captcha_base64: Optional[bytes] = None,
176
- **kwargs,
189
+ image_key: str = "body",
190
+ captcha_link: str | None = None,
191
+ captcha_file: str | None = None,
192
+ captcha_base64: bytes | None = None,
193
+ **kwargs: dict[str, Any],
177
194
  ):
178
195
  # if a local file link is passed
179
196
  if captcha_file:
180
197
  self.create_task_payload["task"].update(
181
- {"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")}
198
+ {image_key: base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")}
182
199
  )
183
200
  # if the file is transferred in base64 encoding
184
201
  elif captcha_base64:
185
202
  self.create_task_payload["task"].update(
186
- {"body": base64.b64encode(captcha_base64).decode("utf-8")}
203
+ {image_key: base64.b64encode(captcha_base64).decode("utf-8")}
187
204
  )
188
205
  # if a URL is passed
189
206
  elif captcha_link:
@@ -192,7 +209,9 @@ class BaseCaptcha:
192
209
  # according to the value of the passed parameter, select the function to save the image
193
210
  if save_format == SaveFormatsEnm.CONST.value:
194
211
  self._file_const_saver(content, file_path, file_extension=file_extension)
195
- self.create_task_payload["task"].update({"body": base64.b64encode(content).decode("utf-8")})
212
+ self.create_task_payload["task"].update(
213
+ {image_key: base64.b64encode(content).decode("utf-8")}
214
+ )
196
215
  except Exception as error:
197
216
  self.result.errorId = 12
198
217
  self.result.errorCode = self.NO_CAPTCHA_ERR
@@ -204,23 +223,24 @@ class BaseCaptcha:
204
223
 
205
224
  async def _aio_body_file_processing(
206
225
  self,
207
- save_format: SaveFormatsEnm,
226
+ save_format: SaveFormatsEnm | str,
208
227
  file_path: str,
209
228
  file_extension: str = "png",
229
+ image_key: str = "body",
210
230
  captcha_link: Optional[str] = None,
211
231
  captcha_file: Optional[str] = None,
212
232
  captcha_base64: Optional[bytes] = None,
213
- **kwargs,
233
+ **kwargs: dict[str, Any],
214
234
  ):
215
235
  # if a local file link is passed
216
236
  if captcha_file:
217
237
  self.create_task_payload["task"].update(
218
- {"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")}
238
+ {image_key: base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")}
219
239
  )
220
240
  # if the file is transferred in base64 encoding
221
241
  elif captcha_base64:
222
242
  self.create_task_payload["task"].update(
223
- {"body": base64.b64encode(captcha_base64).decode("utf-8")}
243
+ {image_key: base64.b64encode(captcha_base64).decode("utf-8")}
224
244
  )
225
245
  # if a URL is passed
226
246
  elif captcha_link:
@@ -229,7 +249,9 @@ class BaseCaptcha:
229
249
  # according to the value of the passed parameter, select the function to save the image
230
250
  if save_format == SaveFormatsEnm.CONST.value:
231
251
  self._file_const_saver(content, file_path, file_extension=file_extension)
232
- self.create_task_payload["task"].update({"body": base64.b64encode(content).decode("utf-8")})
252
+ self.create_task_payload["task"].update(
253
+ {image_key: base64.b64encode(content).decode("utf-8")}
254
+ )
233
255
  except Exception as error:
234
256
  self.result.errorId = 12
235
257
  self.result.errorCode = self.NO_CAPTCHA_ERR
@@ -1,3 +1,5 @@
1
+ from typing import Generator
2
+
1
3
  from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt
2
4
  from requests.adapters import Retry
3
5
 
@@ -8,7 +10,7 @@ APP_KEY = "1899"
8
10
 
9
11
 
10
12
  # Connection retry generator
11
- def attempts_generator(amount: int = 20):
13
+ def attempts_generator(amount: int = 20) -> Generator[int, None, None]:
12
14
  """
13
15
  Function generates a generator of length equal to `amount`
14
16
 
@@ -164,3 +164,15 @@ class atbCaptchaEnm(str, MyEnum):
164
164
  class ProsopoEnm(str, MyEnum):
165
165
  ProsopoTask = "ProsopoTask"
166
166
  ProsopoTaskProxyless = "ProsopoTaskProxyless "
167
+
168
+
169
+ class CaptchaFoxEnm(str, MyEnum):
170
+ CaptchaFoxTask = "CaptchaFoxTask"
171
+
172
+
173
+ class VKCaptchaEnm(str, MyEnum):
174
+ VKCaptchaTask = "VKCaptchaTask"
175
+
176
+
177
+ class TemuCaptchaEnm(str, MyEnum):
178
+ TemuCaptchaTask = "TemuCaptchaTask"
@@ -0,0 +1,116 @@
1
+ import time
2
+ import asyncio
3
+ import logging
4
+ from typing import Any
5
+
6
+ import aiohttp
7
+ import requests
8
+
9
+ from .config import attempts_generator
10
+ from .serializer import GetTaskResultRequestSer, GetTaskResultResponseSer
11
+
12
+
13
+ def get_sync_result(
14
+ get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str
15
+ ) -> dict[str, str]:
16
+ """
17
+ Periodically sends a synchronous request to a remote service to retrieve the result
18
+ of a CAPTCHA-solving task.
19
+
20
+ This function polls the service using blocking HTTP requests until the CAPTCHA is solved,
21
+ an error is returned, or the task times out. It handles intermediate states and retries
22
+ with a configurable sleep interval between attempts.
23
+
24
+ Args:
25
+ get_payload (GetTaskResultRequestSer):
26
+ Serialized request object containing the task ID and payload data.
27
+ sleep_time (int):
28
+ Time in seconds to wait between polling attempts when the task is still processing.
29
+ url_response (str):
30
+ Endpoint URL to query for the CAPTCHA-solving result.
31
+
32
+ Returns:
33
+ dict[str, str]:
34
+ A dictionary containing the final task result. If the task fails or an exception
35
+ occurs, the dictionary includes error details such as status, errorId, errorCode,
36
+ and errorDescription.
37
+ """
38
+ logging.warning(f"{url_response = }")
39
+ response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId)
40
+ # generator for repeated attempts to connect to the server
41
+ attempts = attempts_generator()
42
+ for _ in attempts:
43
+ try:
44
+ # send a request for the result of solving the captcha
45
+ result: dict[str, Any] = requests.post(url_response, json=get_payload.to_dict()).json()
46
+ logging.info(f"Received captcha sync result - {result = }")
47
+ response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId)
48
+ # if the captcha has not been resolved yet, wait
49
+ if response_ser.status == "processing":
50
+ time.sleep(sleep_time)
51
+ continue
52
+ elif response_ser.status == "ready":
53
+ break
54
+ elif response_ser.errorId != 0:
55
+ return response_ser.to_dict()
56
+ except Exception as error:
57
+ response_ser.status = "failed"
58
+ response_ser.errorId = 12
59
+ response_ser.errorCode = "System error"
60
+ response_ser.errorDescription = str(error)
61
+ return response_ser.to_dict()
62
+
63
+
64
+ async def get_async_result(
65
+ get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str
66
+ ) -> dict[str, str]:
67
+ """
68
+ Periodically sends an asynchronous request to a remote service to retrieve the result
69
+ of a CAPTCHA-solving task.
70
+
71
+ This function polls the service at regular intervals until the CAPTCHA is solved,
72
+ an error occurs, or the task times out. It uses aiohttp for asynchronous HTTP requests
73
+ and handles various response states including 'processing', 'ready', and error conditions.
74
+
75
+ Args:
76
+ get_payload (GetTaskResultRequestSer):
77
+ Serialized request object containing the task ID and payload data.
78
+ sleep_time (int):
79
+ Time in seconds to wait between polling attempts when the task is still processing.
80
+ url_response (str):
81
+ Endpoint URL to query for the CAPTCHA-solving result.
82
+
83
+ Returns:
84
+ dict[str, str]:
85
+ A dictionary containing the final task result if successful or partially failed.
86
+ If an exception occurs during the request, returns a dictionary with error details
87
+ including status, errorId, errorCode, and errorDescription.
88
+ """
89
+ response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId)
90
+ # generator for repeated attempts to connect to the server
91
+ attempts = attempts_generator()
92
+ async with aiohttp.ClientSession() as session:
93
+ for _ in attempts:
94
+ try:
95
+ # send a request for the result of solving the captcha
96
+ async with session.post(
97
+ url_response, json=get_payload.to_dict(), raise_for_status=True
98
+ ) as resp:
99
+ result: dict[str, Any] = await resp.json(content_type=None)
100
+ logging.info(f"Received captcha async result - {result = }")
101
+ response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId)
102
+
103
+ # if the captcha has not been resolved yet, wait
104
+ if response_ser.status == "processing":
105
+ await asyncio.sleep(sleep_time)
106
+ continue
107
+ elif response_ser.status == "ready":
108
+ break
109
+ elif response_ser.errorId != 0:
110
+ return response_ser.to_dict()
111
+ except Exception as error:
112
+ response_ser.status = "failed"
113
+ response_ser.errorId = 12
114
+ response_ser.errorCode = "System error"
115
+ response_ser.errorDescription = str(error)
116
+ return response_ser.to_dict()
@@ -0,0 +1,98 @@
1
+ from typing import Any, Literal
2
+ from decimal import Decimal
3
+ from datetime import date, datetime
4
+
5
+ from msgspec import Struct
6
+
7
+ from . import enums
8
+ from .config import APP_KEY
9
+
10
+
11
+ class MyBaseModel(Struct):
12
+ def to_dict(self) -> dict[str, Any]:
13
+ result = {}
14
+ for field in self.__struct_fields__:
15
+ value = getattr(self, field)
16
+
17
+ if isinstance(value, MyBaseModel):
18
+ result[field] = value.to_dict()
19
+
20
+ elif isinstance(value, (list, tuple)) and all(isinstance(el, Struct) for el in value):
21
+ result[field] = [el.to_dict() for el in value]
22
+
23
+ elif isinstance(value, (date, datetime)):
24
+ result[field] = value.isoformat()
25
+
26
+ elif isinstance(value, Decimal):
27
+ result[field] = str(value)
28
+
29
+ else:
30
+ result[field] = value
31
+
32
+ return result
33
+
34
+
35
+ """
36
+ HTTP API Serializers
37
+ """
38
+
39
+
40
+ class TaskSer(MyBaseModel):
41
+ type: str
42
+
43
+
44
+ class CreateTaskBaseSer(MyBaseModel):
45
+ clientKey: str
46
+ task: TaskSer
47
+ languagePool: str = "en"
48
+ callbackUrl: str | None = None
49
+ soft_id: Literal[APP_KEY] = APP_KEY
50
+
51
+
52
+ class GetTaskResultRequestSer(MyBaseModel):
53
+ clientKey: str
54
+ taskId: int | None = None
55
+
56
+
57
+ class CaptchaOptionsSer(MyBaseModel):
58
+ sleep_time: int = 10
59
+ service_type: enums.ServiceEnm | str = enums.ServiceEnm.TWOCAPTCHA
60
+
61
+ url_request: str = f"http://api.{enums.ServiceEnm.TWOCAPTCHA.value}.com/2captcha/in.php"
62
+ url_response: str = f"http://api.{enums.ServiceEnm.TWOCAPTCHA.value}.com/2captcha/res.php"
63
+
64
+ def urls_set(self):
65
+ """
66
+ Set request/response URLs if they not set previously
67
+ """
68
+ if isinstance(self.service_type, enums.ServiceEnm):
69
+ self.service_type = self.service_type.value
70
+
71
+ if self.service_type == enums.ServiceEnm.DEATHBYCAPTCHA:
72
+ self.url_request = f"http://api.{self.service_type}.com/2captcha/in.php"
73
+ self.url_response = f"http://api.{self.service_type}.com/2captcha/res.php"
74
+ else:
75
+ self.url_request = f"https://api.{self.service_type}.com/createTask"
76
+ self.url_response = f"https://api.{self.service_type}.com/getTaskResult"
77
+
78
+
79
+ """
80
+ HTTP API Response
81
+ """
82
+
83
+
84
+ class GetTaskResultResponseSer(MyBaseModel):
85
+ status: str = "ready"
86
+ solution: dict[str, str] | None = None
87
+ cost: float = 0.0
88
+ ip: str | None = None
89
+ createTime: int | None = None
90
+ endTime: int | None = None
91
+ solveCount: int | None = None
92
+ taskId: int | None = None
93
+ # control method params
94
+ balance: float | None = None
95
+ # error info
96
+ errorId: int = 0
97
+ errorCode: str | None = None
98
+ errorDescription: str | None = None
@@ -34,7 +34,7 @@ class DataDomeCaptcha(BaseCaptcha):
34
34
  >>> DataDomeCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
35
35
  ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651",
36
36
  ... captchaUrl="https://rucaptcha.com/demo/hcaptcha",
37
- ... userAgent="https://rucaptcha.com/demo/hcaptcha",
37
+ ... userAgent="Mozilla/5.0 .....",
38
38
  ... proxyType="socks5",
39
39
  ... proxyAddress="1.2.3.4",
40
40
  ... proxyPort="445",
@@ -56,7 +56,7 @@ class DataDomeCaptcha(BaseCaptcha):
56
56
  >>> await DataDomeCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
57
57
  ... websiteURL="3ceb8624-1970-4e6b-91d5-70317b70b651",
58
58
  ... captchaUrl="https://rucaptcha.com/demo/hcaptcha",
59
- ... userAgent="https://rucaptcha.com/demo/hcaptcha",
59
+ ... userAgent="Mozilla/5.0 .....",
60
60
  ... proxyType="socks5",
61
61
  ... proxyAddress="1.2.3.4",
62
62
  ... proxyPort="445",
@@ -1,5 +1,5 @@
1
1
  import shutil
2
- from typing import Union, Optional
2
+ from typing import Any, Union, Optional
3
3
 
4
4
  from .core.base import BaseCaptcha
5
5
  from .core.enums import SaveFormatsEnm, ImageCaptchaEnm
@@ -181,8 +181,8 @@ class ImageCaptcha(BaseCaptcha):
181
181
  captcha_link: Optional[str] = None,
182
182
  captcha_file: Optional[str] = None,
183
183
  captcha_base64: Optional[bytes] = None,
184
- **kwargs,
185
- ) -> dict:
184
+ **kwargs: dict[str, Any],
185
+ ) -> dict[str, Any]:
186
186
  """
187
187
  Sync solving method
188
188
 
@@ -215,8 +215,8 @@ class ImageCaptcha(BaseCaptcha):
215
215
  captcha_link: Optional[str] = None,
216
216
  captcha_file: Optional[str] = None,
217
217
  captcha_base64: Optional[bytes] = None,
218
- **kwargs,
219
- ) -> dict:
218
+ **kwargs: dict[str, Any],
219
+ ) -> dict[str, Any]:
220
220
  """
221
221
  Async solving method
222
222