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.
@@ -1 +1 @@
1
- __version__ = "6.4.0"
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()
@@ -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
 
@@ -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 Union
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
- ) -> Union[dict, Exception]:
15
+ ) -> dict[str, str]:
16
16
  """
17
- Function periodically send the SYNC request to service and wait for captcha solving result
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
- captcha_response = GetTaskResultResponseSer(
25
- **requests.post(url_response, json=get_payload.to_dict()).json(), taskId=get_payload.taskId
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 captcha_response.status == "processing":
48
+ if response_ser.status == "processing":
30
49
  time.sleep(sleep_time)
31
50
  continue
32
- elif captcha_response.status == "ready":
51
+ elif response_ser.status == "ready":
33
52
  break
34
- elif captcha_response.errorId != 0:
35
- return captcha_response.to_dict()
53
+ elif response_ser.errorId != 0:
54
+ return response_ser.to_dict()
36
55
  except Exception as error:
37
- return error
38
- return captcha_response.to_dict()
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
- ) -> Union[dict, Exception]:
65
+ ) -> dict[str, str]:
44
66
  """
45
- Function periodically send the ASYNC request to service and wait for captcha solving result
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
- captcha_response = await resp.json(content_type=None)
57
- captcha_response = GetTaskResultResponseSer(**captcha_response, taskId=get_payload.taskId)
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 captcha_response.status == "processing":
103
+ if response_ser.status == "processing":
61
104
  await asyncio.sleep(sleep_time)
62
105
  continue
63
- elif captcha_response.status == "ready":
106
+ elif response_ser.status == "ready":
64
107
  break
65
- elif captcha_response.errorId != 0:
66
- return captcha_response.to_dict()
108
+ elif response_ser.errorId != 0:
109
+ return response_ser.to_dict()
67
110
  except Exception as error:
68
- return error
69
- return captcha_response.to_dict()
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()