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.
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/LICENSE +1 -1
- {python_rucaptcha-6.3.2/src/python_rucaptcha.egg-info → python_rucaptcha-6.5.0}/PKG-INFO +6 -6
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/pyproject.toml +5 -3
- python_rucaptcha-6.5.0/src/python_rucaptcha/__version__.py +1 -0
- python_rucaptcha-6.5.0/src/python_rucaptcha/captcha_fox.py +124 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/base.py +49 -27
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/config.py +3 -1
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/enums.py +12 -0
- python_rucaptcha-6.5.0/src/python_rucaptcha/core/result_handler.py +116 -0
- python_rucaptcha-6.5.0/src/python_rucaptcha/core/serializer.py +98 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/datadome_captcha.py +2 -2
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/image_captcha.py +5 -5
- python_rucaptcha-6.5.0/src/python_rucaptcha/temu_captcha.py +152 -0
- python_rucaptcha-6.5.0/src/python_rucaptcha/vk_captcha.py +120 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0/src/python_rucaptcha.egg-info}/PKG-INFO +6 -6
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/SOURCES.txt +3 -0
- python_rucaptcha-6.3.2/src/python_rucaptcha/__version__.py +0 -1
- python_rucaptcha-6.3.2/src/python_rucaptcha/core/result_handler.py +0 -69
- python_rucaptcha-6.3.2/src/python_rucaptcha/core/serializer.py +0 -74
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/MANIFEST.in +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/README.md +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/setup.cfg +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/__init__.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/amazon_waf.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/atb_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/audio_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/bounding_box_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/capy_puzzle.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/control.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/coordinates_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/core/__init__.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/cutcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/cyber_siara_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/draw_around_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/friendly_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/fun_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/gee_test.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/grid_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/hcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/key_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/lemin_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/mt_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/prosopo.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/re_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/rotate_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/tencent.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/text_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha/turnstile.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/dependency_links.txt +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/requires.txt +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/src/python_rucaptcha.egg-info/top_level.txt +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_amazon.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_audio.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_bounding_box.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_capypuzzle.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_control.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_coordinates.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_core.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_cutcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_cybersiara.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_datadome.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_draw_around.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_friendly_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_funcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_geetest.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_grid.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_hcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_image.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_key_captcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_lemin.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_mtcaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_recaptcha.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_rotate.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_tencent.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_text.py +0 -0
- {python_rucaptcha-6.3.2 → python_rucaptcha-6.5.0}/tests/test_turnstile.py +0 -0
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-rucaptcha
|
|
3
|
-
Version: 6.
|
|
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
|
|
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 =
|
|
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
|
|
34
|
-
**kwargs,
|
|
33
|
+
service_type: ServiceEnm | str = ServiceEnm.TWOCAPTCHA,
|
|
34
|
+
**kwargs: dict[str, Any],
|
|
35
35
|
):
|
|
36
36
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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)
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
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="
|
|
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="
|
|
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
|
|