python-rucaptcha 6.4.0__py3-none-any.whl → 6.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_rucaptcha/__version__.py +1 -1
- python_rucaptcha/altcha_captcha.py +152 -0
- python_rucaptcha/binance_captcha.py +155 -0
- python_rucaptcha/core/base.py +49 -27
- python_rucaptcha/core/config.py +3 -1
- python_rucaptcha/core/enums.py +29 -0
- python_rucaptcha/core/result_handler.py +69 -23
- python_rucaptcha/core/serializer.py +43 -19
- python_rucaptcha/image_captcha.py +5 -5
- python_rucaptcha/temu_captcha.py +152 -0
- python_rucaptcha/vk_captcha.py +225 -0
- python_rucaptcha/yandex_smart_captcha.py +246 -0
- python_rucaptcha/yidun_captcha.py +171 -0
- python_rucaptcha-6.6.0.dist-info/METADATA +194 -0
- {python_rucaptcha-6.4.0.dist-info → python_rucaptcha-6.6.0.dist-info}/RECORD +18 -12
- {python_rucaptcha-6.4.0.dist-info → python_rucaptcha-6.6.0.dist-info}/WHEEL +1 -1
- python_rucaptcha-6.4.0.dist-info/METADATA +0 -107
- {python_rucaptcha-6.4.0.dist-info → python_rucaptcha-6.6.0.dist-info}/licenses/LICENSE +0 -0
- {python_rucaptcha-6.4.0.dist-info → python_rucaptcha-6.6.0.dist-info}/top_level.txt +0 -0
python_rucaptcha/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "6.
|
|
1
|
+
__version__ = "6.6.0"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
|
|
3
|
+
from .core.base import BaseCaptcha
|
|
4
|
+
from .core.enums import AltchaEnm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AltchaCaptcha(BaseCaptcha):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
websiteURL: str,
|
|
11
|
+
method: Union[str, AltchaEnm] = AltchaEnm.AltchaTaskProxyless,
|
|
12
|
+
challengeURL: Optional[str] = None,
|
|
13
|
+
challengeJSON: Optional[str] = None,
|
|
14
|
+
proxyType: Optional[str] = None,
|
|
15
|
+
proxyAddress: Optional[str] = None,
|
|
16
|
+
proxyPort: Optional[int] = None,
|
|
17
|
+
proxyLogin: Optional[str] = None,
|
|
18
|
+
proxyPassword: Optional[str] = None,
|
|
19
|
+
*args,
|
|
20
|
+
**kwargs,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
The class is used to work with ALTCHA captcha.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
rucaptcha_key: User API key
|
|
27
|
+
websiteURL: Full URL of the captcha page
|
|
28
|
+
method: Captcha type
|
|
29
|
+
challengeURL: Full URL of the page that contains ALTCHA challenge
|
|
30
|
+
challengeJSON: JSON-encoded ALTCHA challenge data
|
|
31
|
+
proxyType: Proxy type (http, https, socks4, socks5)
|
|
32
|
+
proxyAddress: Proxy IP address or hostname
|
|
33
|
+
proxyPort: Proxy port
|
|
34
|
+
proxyLogin: Proxy login
|
|
35
|
+
proxyPassword: Proxy password
|
|
36
|
+
kwargs: Not required params for task creation request
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
>>> AltchaCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
|
|
40
|
+
... websiteURL="https://example.com",
|
|
41
|
+
... challengeURL="https://example.com/altcha/challenge.js",
|
|
42
|
+
... method=AltchaEnm.AltchaTaskProxyless.value,
|
|
43
|
+
... ).captcha_handler()
|
|
44
|
+
{
|
|
45
|
+
"errorId":0,
|
|
46
|
+
"status":"ready",
|
|
47
|
+
"solution":{
|
|
48
|
+
"token":"..."
|
|
49
|
+
},
|
|
50
|
+
"cost":"0.00145",
|
|
51
|
+
"ip":"1.2.3.4",
|
|
52
|
+
"createTime":1692863536,
|
|
53
|
+
"endTime":1692863556,
|
|
54
|
+
"solveCount":1,
|
|
55
|
+
"taskId": 73243152973,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
>>> await AltchaCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
|
|
59
|
+
... websiteURL="https://example.com",
|
|
60
|
+
... challengeJSON='{"挑战数据"}',
|
|
61
|
+
... method=AltchaEnm.AltchaTask.value,
|
|
62
|
+
... proxyType="http",
|
|
63
|
+
... proxyAddress="1.2.3.4",
|
|
64
|
+
... proxyPort=8080,
|
|
65
|
+
... ).aio_captcha_handler()
|
|
66
|
+
{
|
|
67
|
+
"errorId":0,
|
|
68
|
+
"status":"ready",
|
|
69
|
+
"solution":{
|
|
70
|
+
"token":"..."
|
|
71
|
+
},
|
|
72
|
+
"cost":"0.00145",
|
|
73
|
+
"ip":"1.2.3.4",
|
|
74
|
+
"createTime":1692863536,
|
|
75
|
+
"endTime":1692863556,
|
|
76
|
+
"solveCount":1,
|
|
77
|
+
"taskId": 73243152973,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict with full server response
|
|
82
|
+
|
|
83
|
+
Notes:
|
|
84
|
+
https://rucaptcha.com/api-docs/altcha
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
super().__init__(method=method, *args, **kwargs)
|
|
88
|
+
|
|
89
|
+
# XOR validation: exactly one of challengeURL or challengeJSON must be provided
|
|
90
|
+
if not (bool(challengeURL) ^ bool(challengeJSON)):
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"Exactly one of 'challengeURL' or 'challengeJSON' must be provided, not both or neither"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Validate method
|
|
96
|
+
if method not in AltchaEnm.list_values():
|
|
97
|
+
raise ValueError(f"Invalid method parameter set, available - {AltchaEnm.list_values()}")
|
|
98
|
+
|
|
99
|
+
# Build task payload
|
|
100
|
+
task_data = {"websiteURL": websiteURL}
|
|
101
|
+
|
|
102
|
+
if challengeURL:
|
|
103
|
+
task_data["challengeURL"] = challengeURL
|
|
104
|
+
|
|
105
|
+
if challengeJSON:
|
|
106
|
+
task_data["challengeJSON"] = challengeJSON
|
|
107
|
+
|
|
108
|
+
# Add proxy params only for non-proxyless methods
|
|
109
|
+
if method == AltchaEnm.AltchaTask.value:
|
|
110
|
+
if not all([proxyType, proxyAddress, proxyPort]):
|
|
111
|
+
raise ValueError(
|
|
112
|
+
"Proxy parameters (proxyType, proxyAddress, proxyPort) are required for AltchaTask"
|
|
113
|
+
)
|
|
114
|
+
task_data.update(
|
|
115
|
+
{
|
|
116
|
+
"proxyType": proxyType,
|
|
117
|
+
"proxyAddress": proxyAddress,
|
|
118
|
+
"proxyPort": proxyPort,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
if proxyLogin and proxyPassword:
|
|
122
|
+
task_data["proxyLogin"] = proxyLogin
|
|
123
|
+
task_data["proxyPassword"] = proxyPassword
|
|
124
|
+
|
|
125
|
+
self.create_task_payload["task"].update(task_data)
|
|
126
|
+
|
|
127
|
+
def captcha_handler(self, **kwargs) -> dict:
|
|
128
|
+
"""
|
|
129
|
+
Sync solving method
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
kwargs: Parameters for the `requests` library
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict with full server response
|
|
136
|
+
|
|
137
|
+
Notes:
|
|
138
|
+
Check class docstirng for more info
|
|
139
|
+
"""
|
|
140
|
+
return self._processing_response(**kwargs)
|
|
141
|
+
|
|
142
|
+
async def aio_captcha_handler(self) -> dict:
|
|
143
|
+
"""
|
|
144
|
+
Async solving method
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dict with full server response
|
|
148
|
+
|
|
149
|
+
Notes:
|
|
150
|
+
Check class docstirng for more info
|
|
151
|
+
"""
|
|
152
|
+
return await self._aio_processing_response()
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
|
|
3
|
+
from .core.base import BaseCaptcha
|
|
4
|
+
from .core.enums import BinanceCaptchaEnm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BinanceCaptcha(BaseCaptcha):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
websiteURL: str,
|
|
11
|
+
websiteKey: str,
|
|
12
|
+
validateId: str,
|
|
13
|
+
method: Union[str, BinanceCaptchaEnm] = BinanceCaptchaEnm.BinanceTaskProxyless,
|
|
14
|
+
userAgent: Optional[str] = None,
|
|
15
|
+
proxyType: Optional[str] = None,
|
|
16
|
+
proxyAddress: Optional[str] = None,
|
|
17
|
+
proxyPort: Optional[int] = None,
|
|
18
|
+
proxyLogin: Optional[str] = None,
|
|
19
|
+
proxyPassword: Optional[str] = None,
|
|
20
|
+
*args,
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
The class is used to work with Binance CAPTCHA.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
rucaptcha_key: User API key
|
|
28
|
+
websiteURL: Full URL of the page where the captcha is loaded
|
|
29
|
+
websiteKey: Value of bizId, bizType, or bizCode from page requests
|
|
30
|
+
validateId: Dynamic value of validateId, securityId, or securityCheckResponseValidateId
|
|
31
|
+
method: Captcha type
|
|
32
|
+
userAgent: User-Agent string to be used when solving the captcha
|
|
33
|
+
proxyType: Proxy type (http, https, socks4, socks5)
|
|
34
|
+
proxyAddress: Proxy IP address or hostname
|
|
35
|
+
proxyPort: Proxy port
|
|
36
|
+
proxyLogin: Proxy login
|
|
37
|
+
proxyPassword: Proxy password
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
>>> BinanceCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
|
|
41
|
+
... websiteURL="https://example.com/page-with-binance",
|
|
42
|
+
... websiteKey="login",
|
|
43
|
+
... validateId="cb0bfefa598...e54ecd57b",
|
|
44
|
+
... userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
45
|
+
... "(KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
|
46
|
+
... method=BinanceCaptchaEnm.BinanceTaskProxyless.value,
|
|
47
|
+
... ).captcha_handler()
|
|
48
|
+
{
|
|
49
|
+
"errorId":0,
|
|
50
|
+
"status":"ready",
|
|
51
|
+
"solution":{
|
|
52
|
+
"token":"captcha#09ba4905a79f44f...kc99maS943qIsquNP9D77",
|
|
53
|
+
"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
|
54
|
+
},
|
|
55
|
+
"cost":"0.00299",
|
|
56
|
+
"ip":"1.2.3.4",
|
|
57
|
+
"createTime":1692863536,
|
|
58
|
+
"endTime":1692863556,
|
|
59
|
+
"solveCount":1,
|
|
60
|
+
"taskId": 73243152973,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
>>> await BinanceCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
|
|
64
|
+
... websiteURL="https://example.com/page-with-binance",
|
|
65
|
+
... websiteKey="login",
|
|
66
|
+
... validateId="cb0bfefa598...e54ecd57b",
|
|
67
|
+
... method=BinanceCaptchaEnm.BinanceTask.value,
|
|
68
|
+
... proxyType="http",
|
|
69
|
+
... proxyAddress="1.2.3.4",
|
|
70
|
+
... proxyPort=8080,
|
|
71
|
+
... ).aio_captcha_handler()
|
|
72
|
+
{
|
|
73
|
+
"errorId":0,
|
|
74
|
+
"status":"ready",
|
|
75
|
+
"solution":{
|
|
76
|
+
"token":"captcha#09ba4905a79f44f...kc99maS943qIsquNP9D77",
|
|
77
|
+
"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
|
78
|
+
},
|
|
79
|
+
"cost":"0.00299",
|
|
80
|
+
"ip":"1.2.3.4",
|
|
81
|
+
"createTime":1692863536,
|
|
82
|
+
"endTime":1692863556,
|
|
83
|
+
"solveCount":1,
|
|
84
|
+
"taskId": 73243152973,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with full server response
|
|
89
|
+
|
|
90
|
+
Notes:
|
|
91
|
+
https://2captcha.com/api-docs/binance-captcha
|
|
92
|
+
|
|
93
|
+
https://rucaptcha.com/api-docs/binance-captcha
|
|
94
|
+
"""
|
|
95
|
+
super().__init__(method=method, *args, **kwargs)
|
|
96
|
+
|
|
97
|
+
# Validate method
|
|
98
|
+
if method not in BinanceCaptchaEnm.list_values():
|
|
99
|
+
raise ValueError(f"Invalid method parameter set, available - {BinanceCaptchaEnm.list_values()}")
|
|
100
|
+
|
|
101
|
+
# Build task payload
|
|
102
|
+
task_data = {
|
|
103
|
+
"websiteURL": websiteURL,
|
|
104
|
+
"websiteKey": websiteKey,
|
|
105
|
+
"validateId": validateId,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if userAgent:
|
|
109
|
+
task_data["userAgent"] = userAgent
|
|
110
|
+
|
|
111
|
+
# Add proxy params only for non-proxyless methods
|
|
112
|
+
if method == BinanceCaptchaEnm.BinanceTask.value:
|
|
113
|
+
if not all([proxyType, proxyAddress, proxyPort]):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"Proxy parameters (proxyType, proxyAddress, proxyPort) are required for BinanceTask"
|
|
116
|
+
)
|
|
117
|
+
task_data.update(
|
|
118
|
+
{
|
|
119
|
+
"proxyType": proxyType,
|
|
120
|
+
"proxyAddress": proxyAddress,
|
|
121
|
+
"proxyPort": proxyPort,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
if proxyLogin and proxyPassword:
|
|
125
|
+
task_data["proxyLogin"] = proxyLogin
|
|
126
|
+
task_data["proxyPassword"] = proxyPassword
|
|
127
|
+
|
|
128
|
+
self.create_task_payload["task"].update(task_data)
|
|
129
|
+
|
|
130
|
+
def captcha_handler(self, **kwargs) -> dict:
|
|
131
|
+
"""
|
|
132
|
+
Sync solving method
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
kwargs: Parameters for the `requests` library
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Dict with full server response
|
|
139
|
+
|
|
140
|
+
Notes:
|
|
141
|
+
Check class docstirng for more info
|
|
142
|
+
"""
|
|
143
|
+
return self._processing_response(**kwargs)
|
|
144
|
+
|
|
145
|
+
async def aio_captcha_handler(self) -> dict:
|
|
146
|
+
"""
|
|
147
|
+
Async solving method
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dict with full server response
|
|
151
|
+
|
|
152
|
+
Notes:
|
|
153
|
+
Check class docstirng for more info
|
|
154
|
+
"""
|
|
155
|
+
return await self._aio_processing_response()
|
python_rucaptcha/core/base.py
CHANGED
|
@@ -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
|
python_rucaptcha/core/config.py
CHANGED
|
@@ -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
|
|
python_rucaptcha/core/enums.py
CHANGED
|
@@ -98,6 +98,11 @@ class TurnstileCaptchaEnm(str, MyEnum):
|
|
|
98
98
|
TurnstileTask = "TurnstileTask"
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
class AltchaEnm(str, MyEnum):
|
|
102
|
+
AltchaTaskProxyless = "AltchaTaskProxyless"
|
|
103
|
+
AltchaTask = "AltchaTask"
|
|
104
|
+
|
|
105
|
+
|
|
101
106
|
class AmazonWAFCaptchaEnm(str, MyEnum):
|
|
102
107
|
AmazonTask = "AmazonTask"
|
|
103
108
|
AmazonTaskProxyless = "AmazonTaskProxyless"
|
|
@@ -168,3 +173,27 @@ class ProsopoEnm(str, MyEnum):
|
|
|
168
173
|
|
|
169
174
|
class CaptchaFoxEnm(str, MyEnum):
|
|
170
175
|
CaptchaFoxTask = "CaptchaFoxTask"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class VKCaptchaEnm(str, MyEnum):
|
|
179
|
+
VKCaptchaTask = "VKCaptchaTask"
|
|
180
|
+
VKCaptchaImageTask = "VKCaptchaImageTask"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TemuCaptchaEnm(str, MyEnum):
|
|
184
|
+
TemuCaptchaTask = "TemuCaptchaTask"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class BinanceCaptchaEnm(str, MyEnum):
|
|
188
|
+
BinanceTaskProxyless = "BinanceTaskProxyless"
|
|
189
|
+
BinanceTask = "BinanceTask"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class YidunEnm(str, MyEnum):
|
|
193
|
+
YidunTaskProxyless = "YidunTaskProxyless"
|
|
194
|
+
YidunTask = "YidunTask"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class YandexSmartCaptchaEnm(str, MyEnum):
|
|
198
|
+
YandexSmartCaptchaTaskProxyless = "YandexSmartCaptchaTaskProxyless"
|
|
199
|
+
YandexSmartCaptchaTask = "YandexSmartCaptchaTask"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import asyncio
|
|
3
3
|
import logging
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
import requests
|
|
@@ -12,38 +12,80 @@ from .serializer import GetTaskResultRequestSer, GetTaskResultResponseSer
|
|
|
12
12
|
|
|
13
13
|
def get_sync_result(
|
|
14
14
|
get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str
|
|
15
|
-
) ->
|
|
15
|
+
) -> dict[str, str]:
|
|
16
16
|
"""
|
|
17
|
-
|
|
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.
|
|
18
37
|
"""
|
|
38
|
+
response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId)
|
|
19
39
|
# generator for repeated attempts to connect to the server
|
|
20
40
|
attempts = attempts_generator()
|
|
21
41
|
for _ in attempts:
|
|
22
42
|
try:
|
|
23
43
|
# send a request for the result of solving the captcha
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
logging.warning(f"{captcha_response = }")
|
|
44
|
+
result: dict[str, Any] = requests.post(url_response, json=get_payload.to_dict()).json()
|
|
45
|
+
logging.info(f"Received captcha sync result - {result = }")
|
|
46
|
+
response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId)
|
|
28
47
|
# if the captcha has not been resolved yet, wait
|
|
29
|
-
if
|
|
48
|
+
if response_ser.status == "processing":
|
|
30
49
|
time.sleep(sleep_time)
|
|
31
50
|
continue
|
|
32
|
-
elif
|
|
51
|
+
elif response_ser.status == "ready":
|
|
33
52
|
break
|
|
34
|
-
elif
|
|
35
|
-
return
|
|
53
|
+
elif response_ser.errorId != 0:
|
|
54
|
+
return response_ser.to_dict()
|
|
36
55
|
except Exception as error:
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
response_ser.status = "failed"
|
|
57
|
+
response_ser.errorId = 12
|
|
58
|
+
response_ser.errorCode = "System error"
|
|
59
|
+
response_ser.errorDescription = str(error)
|
|
60
|
+
return response_ser.to_dict()
|
|
39
61
|
|
|
40
62
|
|
|
41
63
|
async def get_async_result(
|
|
42
64
|
get_payload: GetTaskResultRequestSer, sleep_time: int, url_response: str
|
|
43
|
-
) ->
|
|
65
|
+
) -> dict[str, str]:
|
|
44
66
|
"""
|
|
45
|
-
|
|
67
|
+
Periodically sends an asynchronous request to a remote service to retrieve the result
|
|
68
|
+
of a CAPTCHA-solving task.
|
|
69
|
+
|
|
70
|
+
This function polls the service at regular intervals until the CAPTCHA is solved,
|
|
71
|
+
an error occurs, or the task times out. It uses aiohttp for asynchronous HTTP requests
|
|
72
|
+
and handles various response states including 'processing', 'ready', and error conditions.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
get_payload (GetTaskResultRequestSer):
|
|
76
|
+
Serialized request object containing the task ID and payload data.
|
|
77
|
+
sleep_time (int):
|
|
78
|
+
Time in seconds to wait between polling attempts when the task is still processing.
|
|
79
|
+
url_response (str):
|
|
80
|
+
Endpoint URL to query for the CAPTCHA-solving result.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
dict[str, str]:
|
|
84
|
+
A dictionary containing the final task result if successful or partially failed.
|
|
85
|
+
If an exception occurs during the request, returns a dictionary with error details
|
|
86
|
+
including status, errorId, errorCode, and errorDescription.
|
|
46
87
|
"""
|
|
88
|
+
response_ser = GetTaskResultResponseSer(taskId=get_payload.taskId)
|
|
47
89
|
# generator for repeated attempts to connect to the server
|
|
48
90
|
attempts = attempts_generator()
|
|
49
91
|
async with aiohttp.ClientSession() as session:
|
|
@@ -53,17 +95,21 @@ async def get_async_result(
|
|
|
53
95
|
async with session.post(
|
|
54
96
|
url_response, json=get_payload.to_dict(), raise_for_status=True
|
|
55
97
|
) as resp:
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
result: dict[str, Any] = await resp.json(content_type=None)
|
|
99
|
+
logging.info(f"Received captcha async result - {result = }")
|
|
100
|
+
response_ser = GetTaskResultResponseSer(**result, taskId=get_payload.taskId)
|
|
58
101
|
|
|
59
102
|
# if the captcha has not been resolved yet, wait
|
|
60
|
-
if
|
|
103
|
+
if response_ser.status == "processing":
|
|
61
104
|
await asyncio.sleep(sleep_time)
|
|
62
105
|
continue
|
|
63
|
-
elif
|
|
106
|
+
elif response_ser.status == "ready":
|
|
64
107
|
break
|
|
65
|
-
elif
|
|
66
|
-
return
|
|
108
|
+
elif response_ser.errorId != 0:
|
|
109
|
+
return response_ser.to_dict()
|
|
67
110
|
except Exception as error:
|
|
68
|
-
|
|
69
|
-
|
|
111
|
+
response_ser.status = "failed"
|
|
112
|
+
response_ser.errorId = 12
|
|
113
|
+
response_ser.errorCode = "System error"
|
|
114
|
+
response_ser.errorDescription = str(error)
|
|
115
|
+
return response_ser.to_dict()
|