python-rucaptcha 5.1.5a0__py3-none-any.whl → 5.2.1__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__ = "5.1.5a"
1
+ __version__ = "5.2.1"
@@ -5,7 +5,6 @@ from .core.enums import AmazonWAFCaptchaEnm
5
5
  class AmazonWAF(BaseCaptcha):
6
6
  def __init__(
7
7
  self,
8
- rucaptcha_key: str,
9
8
  pageurl: str,
10
9
  sitekey: str,
11
10
  iv: str,
@@ -56,7 +55,7 @@ class AmazonWAF(BaseCaptcha):
56
55
  Notes:
57
56
  https://rucaptcha.com/api-rucaptcha#amazon-waf
58
57
  """
59
- super().__init__(rucaptcha_key=rucaptcha_key, method=method, *args, **kwargs)
58
+ super().__init__(method=method, *args, **kwargs)
60
59
 
61
60
  self.post_payload.update({"sitekey": sitekey, "pageurl": pageurl, "iv": iv, "context": context})
62
61
 
@@ -0,0 +1,208 @@
1
+ import base64
2
+ import shutil
3
+ from typing import Optional
4
+
5
+ from .core.base import BaseCaptcha
6
+ from .core.enums import SaveFormatsEnm, AudioCaptchaEnm
7
+
8
+
9
+ class AudioCaptcha(BaseCaptcha):
10
+ def __init__(
11
+ self,
12
+ save_format: str = SaveFormatsEnm.TEMP.value,
13
+ audio_clearing: bool = True,
14
+ audio_path: str = "PythonRuCaptchaAudio",
15
+ lang: str = "en",
16
+ *args,
17
+ **kwargs,
18
+ ):
19
+ """
20
+ The class is used to work with Text Captcha.
21
+
22
+ Args:
23
+ rucaptcha_key: User API key
24
+ save_format: The format in which the file will be saved, or as a temporary file - 'temp',
25
+ or as a regular file to a folder created by the library - 'const'.
26
+ audio_clearing: True - delete file after solution, False - don't delete file after solution
27
+ audio_path: Folder to save captcha audio
28
+ lang: Captcha audio lang: `en`, `fr`, `de`, `el`, `pt`, `ru`
29
+
30
+ Examples:
31
+ >>> AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
32
+ ... lang='en'
33
+ ... ).captcha_handler(captcha_file='examples/mediacaptcha_audio/recaptcha_55914.mp3')
34
+ {
35
+ 'captchaSolve': 'five five nine one four',
36
+ 'taskId': 73243152973,
37
+ 'error': False,
38
+ 'errorBody': None
39
+ }
40
+
41
+ >>> await AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
42
+ ... lang='en'
43
+ ... ).aio_captcha_handler(captcha_file='examples/mediacaptcha_audio/recaptcha_55914.mp3')
44
+ {
45
+ 'captchaSolve': 'five five nine one four',
46
+ 'taskId': 73243152973,
47
+ 'error': False,
48
+ 'errorBody': None
49
+ }
50
+
51
+ Returns:
52
+ Dict with full server response
53
+
54
+ Notes:
55
+ https://rucaptcha.com/api-rucaptcha#audio
56
+ """
57
+
58
+ super().__init__(method=AudioCaptchaEnm.AUDIO.value, *args, **kwargs)
59
+ self.save_format = save_format
60
+ self.audio_clearing = audio_clearing
61
+ self.audio_path = audio_path
62
+
63
+ self.post_payload.update({"lang": lang})
64
+
65
+ def captcha_handler(
66
+ self,
67
+ captcha_link: Optional[str] = None,
68
+ captcha_file: Optional[str] = None,
69
+ captcha_base64: Optional[bytes] = None,
70
+ **kwargs,
71
+ ) -> dict:
72
+ """
73
+ Synchronous method for captcha solving
74
+
75
+ Args:
76
+ captcha_link: Captcha file URL
77
+ captcha_file: Captcha file path
78
+ captcha_base64: Captcha file BASE64 info
79
+ kwargs: additional params for `requests` library
80
+
81
+ Examples:
82
+ >>> AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
83
+ ... lang='en'
84
+ ... ).captcha_handler(captcha_file='examples/mediacaptcha_audio/recaptcha_55914.mp3')
85
+ {
86
+ 'captchaSolve': 'five five nine one four',
87
+ 'taskId': 73243152973,
88
+ 'error': False,
89
+ 'errorBody': None
90
+ }
91
+
92
+ >>> AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
93
+ ... lang='en'
94
+ ... ).captcha_handler(captcha_link='http://some/link/address/recaptcha_55914.mp3')
95
+ {
96
+ 'captchaSolve': 'five five nine one four',
97
+ 'taskId': 73243152973,
98
+ 'error': False,
99
+ 'errorBody': None
100
+ }
101
+
102
+ Returns:
103
+ Dict with full server response
104
+
105
+ Notes:
106
+ Check class docstirng for more info
107
+ """
108
+ # if a local file link is passed
109
+ if captcha_file:
110
+ self.post_payload.update({"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")})
111
+ # if the file is transferred in base64 encoding
112
+ elif captcha_base64:
113
+ self.post_payload.update({"body": base64.b64encode(captcha_base64).decode("utf-8")})
114
+ # if a URL is passed
115
+ elif captcha_link:
116
+ try:
117
+ content = self.url_open(url=captcha_link, **kwargs).content
118
+ except Exception as error:
119
+ self.result.error = True
120
+ self.result.errorBody = str(error)
121
+ return self.result.dict()
122
+
123
+ # according to the value of the passed parameter, select the function to save the file
124
+ if self.save_format == SaveFormatsEnm.CONST.value:
125
+ self._file_const_saver(content, self.audio_path, file_extension="mp3")
126
+ self.post_payload.update({"body": base64.b64encode(content).decode("utf-8")})
127
+
128
+ else:
129
+ # if none of the parameters are passed
130
+ self.result.error = True
131
+ self.result.errorBody = self.NO_CAPTCHA_ERR
132
+ return self.result.dict()
133
+
134
+ return self._processing_response(**kwargs)
135
+
136
+ async def aio_captcha_handler(
137
+ self,
138
+ captcha_link: Optional[str] = None,
139
+ captcha_file: Optional[str] = None,
140
+ captcha_base64: Optional[bytes] = None,
141
+ **kwargs,
142
+ ) -> dict:
143
+ """
144
+ Asynchronous method for captcha solving
145
+
146
+ Args:
147
+ captcha_link: Captcha file URL
148
+ captcha_file: Captcha file path
149
+ captcha_base64: Captcha file BASE64
150
+ kwargs: additional params for `aiohttp` library
151
+
152
+ Examples:
153
+ >>> await AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
154
+ ... lang='en'
155
+ ... ).aio_captcha_handler(captcha_file='examples/mediacaptcha_audio/recaptcha_55914.mp3')
156
+ {
157
+ 'captchaSolve': 'five five nine one four',
158
+ 'taskId': 73243152973,
159
+ 'error': False,
160
+ 'errorBody': None
161
+ }
162
+ >>> await AudioCaptcha(rucaptcha_key="aa9011f31111181111168611f1151122",
163
+ ... lang='en'
164
+ ... ).aio_captcha_handler(captcha_link='http://some/link/address/recaptcha_55914.mp3')
165
+ {
166
+ 'captchaSolve': 'five five nine one four',
167
+ 'taskId': 73243152973,
168
+ 'error': False,
169
+ 'errorBody': None
170
+ }
171
+
172
+ Returns:
173
+ Dict with full server response
174
+
175
+ Notes:
176
+ Check class docstirng for more info
177
+ """
178
+ # if a local file link is passed
179
+ if captcha_file:
180
+ self.post_payload.update({"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")})
181
+ # if the file is transferred in base64 encoding
182
+ elif captcha_base64:
183
+ self.post_payload.update({"body": base64.b64encode(captcha_base64).decode("utf-8")})
184
+ # if a URL is passed
185
+ elif captcha_link:
186
+ try:
187
+ content = await self.aio_url_read(url=captcha_link, **kwargs)
188
+ except Exception as error:
189
+ self.result.error = True
190
+ self.result.errorBody = str(error)
191
+ return self.result.dict()
192
+
193
+ # according to the value of the passed parameter, select the function to save the file
194
+ if self.save_format == SaveFormatsEnm.CONST.value:
195
+ self._file_const_saver(content, self.audio_path, file_extension="mp3")
196
+ self.post_payload.update({"body": base64.b64encode(content).decode("utf-8")})
197
+
198
+ else:
199
+ # if none of the parameters are passed
200
+ self.result.error = True
201
+ self.result.errorBody = self.NO_CAPTCHA_ERR
202
+ return self.result.dict()
203
+
204
+ return await self._aio_processing_response()
205
+
206
+ def __del__(self):
207
+ if self.save_format == SaveFormatsEnm.CONST.value and self.audio_clearing:
208
+ shutil.rmtree(self.audio_path)
@@ -37,6 +37,31 @@ class Control(BaseCaptcha):
37
37
  'errorBody': None
38
38
  }
39
39
 
40
+ Death By Captcha example:
41
+
42
+ >>> Control(rucaptcha_key="service_username:service_password",
43
+ ... service_type="other-captcha-services",
44
+ ... action=ControlEnm.GETBALANCE.value).additional_methods()
45
+ {
46
+ 'captchaSolve': '0.00',
47
+ 'taskId': None,
48
+ 'error': False,
49
+ 'errorBody': None
50
+ }
51
+
52
+ Death By Captcha example:
53
+
54
+ >>> from python_rucaptcha.core.enums import ControlEnm, ServiceEnm
55
+ >>> Control(rucaptcha_key="service_username:service_password",
56
+ ... service_type=ServiceEnm.DEATHBYCAPTCHA,
57
+ ... action=ControlEnm.GETBALANCE.value).additional_methods()
58
+ {
59
+ 'captchaSolve': '0.00',
60
+ 'taskId': None,
61
+ 'error': False,
62
+ 'errorBody': None
63
+ }
64
+
40
65
  Returns:
41
66
  Dict with full server response
42
67
 
@@ -46,7 +71,7 @@ class Control(BaseCaptcha):
46
71
  https://rucaptcha.com/api-rucaptcha#additional
47
72
  """
48
73
 
49
- super().__init__(action=action, *args, **kwargs)
74
+ super().__init__(method=ControlEnm.CONTROL.value, action=action, *args, **kwargs)
50
75
 
51
76
  # check user params
52
77
  if action not in ControlEnm.list_values():
File without changes
@@ -0,0 +1,191 @@
1
+ import os
2
+ import time
3
+ import uuid
4
+ import asyncio
5
+ from pathlib import Path
6
+
7
+ import aiohttp
8
+ import requests
9
+ from requests.adapters import HTTPAdapter
10
+
11
+ from . import enums
12
+ from .config import RETRIES, ASYNC_RETRIES
13
+ from .serializer import ResponseSer, GetRequestSer, PostRequestSer, CaptchaOptionsSer, ServicePostResponseSer
14
+ from .result_handler import get_sync_result, get_async_result
15
+
16
+
17
+ class BaseCaptcha:
18
+ NO_CAPTCHA_ERR = "You did not send any file, local link or URL."
19
+
20
+ def __init__(
21
+ self,
22
+ rucaptcha_key: str,
23
+ method: str,
24
+ action: str = "get",
25
+ sleep_time: int = 15,
26
+ service_type: str = enums.ServiceEnm.TWOCAPTCHA.value,
27
+ **kwargs,
28
+ ):
29
+ """
30
+ :param rucaptcha_key: User API key
31
+ :param method: Captcha type
32
+ :param action: Server action
33
+ :param sleep_time: Time to wait for captcha solution
34
+ :param service_type: URL with which the program will work, "2captcha" option is possible (standard)
35
+ and "rucaptcha"
36
+ :param kwargs: Designed to pass OPTIONAL parameters to the payload for a request to RuCaptcha
37
+ """
38
+ # assign args to validator
39
+ self.params = CaptchaOptionsSer(**locals(), **kwargs)
40
+
41
+ # prepare POST payload
42
+ self.post_payload = PostRequestSer(key=self.params.rucaptcha_key, method=method).dict(by_alias=True)
43
+ # prepare GET payload
44
+ self.get_payload = GetRequestSer(key=self.params.rucaptcha_key, action=action).dict(
45
+ by_alias=True, exclude_none=True
46
+ )
47
+ # prepare result payload
48
+ self.result = ResponseSer()
49
+
50
+ for key in kwargs:
51
+ self.post_payload.update({key: kwargs[key]})
52
+
53
+ # prepare session
54
+ self.session = requests.Session()
55
+ self.session.mount("http://", HTTPAdapter(max_retries=RETRIES))
56
+ self.session.mount("https://", HTTPAdapter(max_retries=RETRIES))
57
+
58
+ def _processing_response(self, **kwargs: dict) -> dict:
59
+ """
60
+ Method processing captcha solving task creation result
61
+ :param kwargs: additional params for Requests library
62
+ """
63
+ try:
64
+ response = ServicePostResponseSer(
65
+ **self.session.post(self.params.url_request, data=self.post_payload, **kwargs).json()
66
+ )
67
+ # check response status
68
+ if response.status == 1:
69
+ self.result.taskId = response.request
70
+ else:
71
+ self.result.error = True
72
+ self.result.errorBody = response.request
73
+ except Exception as error:
74
+ self.result.error = True
75
+ self.result.errorBody = str(error)
76
+
77
+ # check for errors while make request to server
78
+ if self.result.error:
79
+ return self.result.dict()
80
+
81
+ # if all is ok - send captcha to service and wait solution
82
+ # update payload - add captcha taskId
83
+ self.get_payload.update({"id": self.result.taskId})
84
+
85
+ # wait captcha solving
86
+ time.sleep(self.params.sleep_time)
87
+ return get_sync_result(
88
+ get_payload=self.get_payload,
89
+ sleep_time=self.params.sleep_time,
90
+ url_response=self.params.url_response,
91
+ result=self.result,
92
+ )
93
+
94
+ def url_open(self, url: str, **kwargs):
95
+ """
96
+ Method open links
97
+ """
98
+ return self.session.get(url=url, **kwargs)
99
+
100
+ async def aio_url_read(self, url: str, **kwargs) -> bytes:
101
+ """
102
+ Async method read bytes from link
103
+ """
104
+ async with aiohttp.ClientSession() as session:
105
+ async for attempt in ASYNC_RETRIES:
106
+ with attempt:
107
+ async with session.get(url=url, **kwargs) as resp:
108
+ return await resp.content.read()
109
+
110
+ async def _aio_processing_response(self) -> dict:
111
+ """
112
+ Method processing async captcha solving task creation result
113
+ """
114
+ try:
115
+ # make async or sync request
116
+ response = await self.__aio_make_post_request()
117
+ # check response status
118
+ if response.status == 1:
119
+ self.result.taskId = response.request
120
+ else:
121
+ self.result.error = True
122
+ self.result.errorBody = response.request
123
+ except Exception as error:
124
+ self.result.error = True
125
+ self.result.errorBody = str(error)
126
+
127
+ # check for errors while make request to server
128
+ if self.result.error:
129
+ return self.result.dict()
130
+
131
+ # if all is ok - send captcha to service and wait solution
132
+ # update payload - add captcha taskId
133
+ self.get_payload.update({"id": self.result.taskId})
134
+
135
+ # wait captcha solving
136
+ await asyncio.sleep(self.params.sleep_time)
137
+ return await get_async_result(
138
+ get_payload=self.get_payload,
139
+ sleep_time=self.params.sleep_time,
140
+ url_response=self.params.url_response,
141
+ result=self.result,
142
+ )
143
+
144
+ async def __aio_make_post_request(self) -> ServicePostResponseSer:
145
+ async with aiohttp.ClientSession() as session:
146
+ async for attempt in ASYNC_RETRIES:
147
+ with attempt:
148
+ async with session.post(
149
+ self.params.url_request, data=self.post_payload, raise_for_status=True
150
+ ) as resp:
151
+ response_json = await resp.json()
152
+ return ServicePostResponseSer(**response_json)
153
+
154
+ # Working with images methods
155
+
156
+ @staticmethod
157
+ def _local_file_captcha(captcha_file: str):
158
+ """
159
+ Method get local file, read it and prepare for sending to Captcha solving service
160
+ """
161
+ with open(captcha_file, "rb") as file:
162
+ return file.read()
163
+
164
+ def _file_const_saver(self, content: bytes, file_path: str, file_extension: str = "png"):
165
+ """
166
+ Method create and save file in folder
167
+ """
168
+ Path(file_path).mkdir(parents=True, exist_ok=True)
169
+
170
+ # generate image name
171
+ self._file_name = f"file-{uuid.uuid4()}.{file_extension}"
172
+
173
+ # save image to folder
174
+ with open(os.path.join(file_path, self._file_name), "wb") as out_image:
175
+ out_image.write(content)
176
+
177
+ def __enter__(self):
178
+ return self
179
+
180
+ def __exit__(self, exc_type, exc_value, traceback):
181
+ if exc_type:
182
+ return False
183
+ return True
184
+
185
+ async def __aenter__(self):
186
+ return self
187
+
188
+ async def __aexit__(self, exc_type, exc_value, traceback):
189
+ if exc_type:
190
+ return False
191
+ return True
@@ -0,0 +1,21 @@
1
+ from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt
2
+ from requests.adapters import Retry
3
+
4
+ RETRIES = Retry(total=5, backoff_factor=0.5)
5
+ ASYNC_RETRIES = AsyncRetrying(wait=wait_fixed(5), stop=stop_after_attempt(5), reraise=True)
6
+ # Application key
7
+ APP_KEY = "1899"
8
+
9
+
10
+ # Connection retry generator
11
+ def attempts_generator(amount: int = 7):
12
+ """
13
+ Function generates a generator of length equal to `amount`
14
+
15
+ Args:
16
+ amount: number of attempts generated
17
+
18
+ Returns:
19
+ Attempt number
20
+ """
21
+ yield from range(1, amount)
@@ -0,0 +1,112 @@
1
+ from enum import Enum
2
+ from typing import List
3
+
4
+
5
+ class MyEnum(Enum):
6
+ """
7
+ Base class for work with updated Enums
8
+ """
9
+
10
+ @classmethod
11
+ def list(cls) -> List[Enum]:
12
+ return list(map(lambda c: c, cls))
13
+
14
+ @classmethod
15
+ def list_values(cls) -> List[str]:
16
+ return list(map(lambda c: c.value, cls))
17
+
18
+ @classmethod
19
+ def list_names(cls) -> List[str]:
20
+ return list(map(lambda c: c.name, cls))
21
+
22
+
23
+ class ServiceEnm(str, MyEnum):
24
+ TWOCAPTCHA = "2captcha"
25
+ RUCAPTCHA = "rucaptcha"
26
+ DEATHBYCAPTCHA = "other-captcha-services"
27
+
28
+
29
+ class SaveFormatsEnm(str, MyEnum):
30
+ TEMP = "temp"
31
+ CONST = "const"
32
+
33
+
34
+ class GeetestEnm(str, MyEnum):
35
+ GEETEST = "geetest"
36
+ GEETEST_V4 = "geetest_v4"
37
+
38
+
39
+ class ImageCaptchaEnm(str, MyEnum):
40
+ BASE64 = "base64"
41
+
42
+
43
+ class CapyPuzzleEnm(str, MyEnum):
44
+ CAPY = "capy"
45
+
46
+
47
+ class FunCaptchaEnm(str, MyEnum):
48
+ FUNCAPTCHA = "funcaptcha"
49
+
50
+
51
+ class ReCaptchaEnm(str, MyEnum):
52
+ USER_RECAPTCHA = "userrecaptcha"
53
+
54
+
55
+ class LeminCroppedCaptchaEnm(str, MyEnum):
56
+ LEMIN = "lemin"
57
+
58
+
59
+ class HCaptchaEnm(str, MyEnum):
60
+ HCAPTCHA = "hcaptcha"
61
+
62
+
63
+ class KeyCaptchaEnm(str, MyEnum):
64
+ KEYCAPTCHA = "keycaptcha"
65
+
66
+
67
+ class RotateCaptchaEnm(str, MyEnum):
68
+ ROTATECAPTCHA = "rotatecaptcha"
69
+
70
+
71
+ class TikTokCaptchaEnm(str, MyEnum):
72
+ TIKTOK = "tiktok"
73
+
74
+
75
+ class ControlEnm(str, MyEnum):
76
+ # control method
77
+ CONTROL = "control"
78
+
79
+ # default
80
+ GET = "get"
81
+ # https://rucaptcha.com/api-rucaptcha#manage_pingback
82
+ ADD_PINGBACK = "add_pingback"
83
+ GET_PINGBACK = "get_pingback"
84
+ DEL_PINGBACK = "del_pingback"
85
+
86
+ # https://rucaptcha.com/api-rucaptcha#additional
87
+ GETBALANCE = "getbalance"
88
+ GET2 = "get2"
89
+
90
+ # https://rucaptcha.com/api-rucaptcha#complain
91
+ REPORTGOOD = "reportgood"
92
+ REPORTBAD = "reportbad"
93
+
94
+
95
+ class YandexSmartCaptchaEnm(str, MyEnum):
96
+ YANDEX = "yandex"
97
+
98
+
99
+ class TurnstileCaptchaEnm(str, MyEnum):
100
+ TURNSTILE = "turnstile"
101
+
102
+
103
+ class AmazonWAFCaptchaEnm(str, MyEnum):
104
+ AMAZON_WAF = "amazon_waf"
105
+
106
+
107
+ class TextCaptchaEnm(str, MyEnum):
108
+ TEXT = "text"
109
+
110
+
111
+ class AudioCaptchaEnm(str, MyEnum):
112
+ AUDIO = "audio"
@@ -0,0 +1,92 @@
1
+ import time
2
+ import asyncio
3
+
4
+ import aiohttp
5
+ import requests
6
+
7
+ from .config import attempts_generator
8
+ from .serializer import ResponseSer, ServiceGetResponseSer
9
+
10
+
11
+ def result_processing(captcha_response: ServiceGetResponseSer, result: ResponseSer) -> dict:
12
+ """
13
+ Function processing service response status values
14
+ """
15
+
16
+ # on error during solving
17
+ if captcha_response.status == 0:
18
+ result.error = True
19
+ result.errorBody = captcha_response.request
20
+
21
+ # if solving is success
22
+ elif captcha_response.status == 1:
23
+ result.error = False
24
+ result.errorBody = None
25
+ result.captchaSolve = captcha_response.request
26
+
27
+ # if this is ReCaptcha v3 then we get it from the server
28
+ if captcha_response.user_check and captcha_response.user_score:
29
+ result = result.dict()
30
+ result.update(
31
+ {
32
+ "user_check": captcha_response.user_check,
33
+ "user_score": captcha_response.user_score,
34
+ }
35
+ )
36
+ return result
37
+
38
+ return result.dict()
39
+
40
+
41
+ def get_sync_result(get_payload: dict, sleep_time: int, url_response: str, result: ResponseSer) -> dict:
42
+ """
43
+ Function periodically send the SYNC request to service and wait for captcha solving result
44
+ """
45
+ # generator for repeated attempts to connect to the server
46
+ attempts = attempts_generator()
47
+ for _ in attempts:
48
+ try:
49
+ # send a request for the result of solving the captcha
50
+ captcha_response = ServiceGetResponseSer(**requests.get(url_response, params=get_payload).json())
51
+ # if the captcha has not been resolved yet, wait
52
+ if captcha_response.request == "CAPCHA_NOT_READY":
53
+ time.sleep(sleep_time)
54
+ result.error = True
55
+ result.errorBody = "ERROR_CAPTCHA_UNSOLVABLE"
56
+ else:
57
+ return result_processing(captcha_response, result)
58
+
59
+ except Exception as error:
60
+ result.error = True
61
+ result.errorBody = str(error)
62
+
63
+ return result.dict()
64
+
65
+
66
+ async def get_async_result(get_payload: dict, sleep_time: int, url_response: str, result: ResponseSer) -> dict:
67
+ """
68
+ Function periodically send the ASYNC request to service and wait for captcha solving result
69
+ """
70
+ # generator for repeated attempts to connect to the server
71
+ attempts = attempts_generator()
72
+ async with aiohttp.ClientSession() as session:
73
+ for _ in attempts:
74
+ try:
75
+ # send a request for the result of solving the captcha
76
+ async with session.get(url_response, params=get_payload, raise_for_status=True) as resp:
77
+ captcha_response = await resp.json()
78
+ captcha_response = ServiceGetResponseSer(**captcha_response)
79
+
80
+ # if the captcha has not been resolved yet, wait
81
+ if captcha_response.request == "CAPCHA_NOT_READY":
82
+ await asyncio.sleep(sleep_time)
83
+ result.error = True
84
+ result.errorBody = "ERROR_CAPTCHA_UNSOLVABLE"
85
+
86
+ else:
87
+ return result_processing(captcha_response, result)
88
+
89
+ except Exception as error:
90
+ result.error = True
91
+ result.errorBody = str(error)
92
+ return result.dict()
@@ -0,0 +1,171 @@
1
+ import logging
2
+ from uuid import uuid4
3
+ from typing import Union, Optional
4
+
5
+ from pydantic import Field, BaseModel, conint, constr, validator, root_validator
6
+
7
+ from . import enums
8
+ from .config import APP_KEY
9
+
10
+ """
11
+ Socket API Serializers
12
+ """
13
+
14
+
15
+ class MyBaseModel(BaseModel):
16
+ class Config:
17
+ validate_assignment = True
18
+
19
+
20
+ class CaptchaOptionsSocketSer(MyBaseModel):
21
+ phrase: bool = False
22
+ caseSensitive: bool = False
23
+ numeric: conint(ge=1, le=4) = 0
24
+ calc: bool = False
25
+ minLen: conint(ge=0, le=20) = 0
26
+ maxLen: conint(ge=0, le=20) = 0
27
+ lang: str = ""
28
+ hintText: constr(max_length=139) = ""
29
+ hintImg: str = ""
30
+ softId: str = Field(APP_KEY, const=True)
31
+
32
+
33
+ class NormalCaptchaSocketSer(BaseModel):
34
+ method: str = "normal"
35
+ requestId: str = Field(default_factory=lambda: str(uuid4()))
36
+ body: str = str()
37
+ options: "CaptchaOptionsSocketSer" = CaptchaOptionsSocketSer()
38
+
39
+
40
+ class TextCaptchaSocketSer(BaseModel):
41
+ method: str = "text"
42
+ requestId: str = Field(default_factory=lambda: str(uuid4()))
43
+ body: str = str()
44
+ options: "CaptchaOptionsSocketSer" = CaptchaOptionsSocketSer()
45
+
46
+
47
+ class ControlCaptchaSocketSer(BaseModel):
48
+ method: str
49
+ requestId: str = Field(default_factory=lambda: str(uuid4()))
50
+ success: str = None
51
+ captchaId: int = None
52
+
53
+
54
+ class SocketResponse(BaseModel):
55
+ method: str = None
56
+ success: bool = None
57
+ code: str = None
58
+ # captcha task ID at RuCaptcha service
59
+ captchaId: int = None
60
+ # manually generated requestID
61
+ requestId: str = Field(default_factory=lambda: str(uuid4()))
62
+ error: str = None
63
+ # specific fields for balance request response
64
+ balance: float = None
65
+ valute: str = None
66
+
67
+
68
+ class SockAuthSer(BaseModel):
69
+ method: str = "auth"
70
+ requestId: str = Field(default_factory=lambda: str(uuid4()))
71
+ key: str
72
+ options: dict
73
+
74
+
75
+ """
76
+ HTTP API Serializers
77
+ """
78
+
79
+
80
+ class PostRequestSer(MyBaseModel):
81
+ key: str
82
+ method: str
83
+ soft_id: str = Field(APP_KEY, const=True)
84
+ field_json: int = Field(1, alias="json")
85
+
86
+
87
+ class GetRequestSer(BaseModel):
88
+ key: str
89
+ action: str = "get"
90
+ field_json: int = Field(1, alias="json")
91
+
92
+ # Control keys
93
+ ids: str = None
94
+ id: str = None
95
+
96
+
97
+ class CaptchaOptionsSer(BaseModel):
98
+ method: str
99
+ action: str
100
+ sleep_time: conint(gt=5) = 10
101
+ service_type: str = enums.ServiceEnm.TWOCAPTCHA.value
102
+ rucaptcha_key: constr(min_length=1)
103
+
104
+ url_request: Optional[str] = None # /in.php
105
+ url_response: Optional[str] = None # /res.php
106
+
107
+ @validator("rucaptcha_key")
108
+ def rucaptcha_key_check(cls, value, values, **kwargs):
109
+ service_type = values.get("service_type")
110
+ if service_type in (enums.ServiceEnm.RUCAPTCHA, enums.ServiceEnm.TWOCAPTCHA):
111
+ if len(value) != 32:
112
+ raise ValueError(f"Invalid `rucaptcha_key` len, it must be - 32, u send - {len(value)}")
113
+ return value
114
+
115
+ @validator("service_type")
116
+ def service_type_check(cls, value):
117
+ if value not in enums.ServiceEnm.list_values():
118
+ logging.warning(
119
+ f"We support only this list of services - '{', '.join(enums.ServiceEnm.list_values())}', u send - '{value}'. "
120
+ f"All other services you use at your own risk"
121
+ )
122
+ return value
123
+
124
+ @root_validator
125
+ def urls_set(cls, values):
126
+ """
127
+ Set request \ response URLs if they not set previously
128
+ """
129
+ if not values.get("url_request") and not values.get("url_response"):
130
+ service_type = values.get("service_type")
131
+ if service_type == enums.ServiceEnm.DEATHBYCAPTCHA:
132
+ values.update(
133
+ {
134
+ "url_request": f"http://api.{service_type}.com/2captcha/in.php",
135
+ "url_response": f"http://api.{service_type}.com/2captcha/res.php",
136
+ }
137
+ )
138
+ else:
139
+ values.update(
140
+ {
141
+ "url_request": f"http://{service_type}.com/in.php",
142
+ "url_response": f"http://{service_type}.com/res.php",
143
+ }
144
+ )
145
+ return values
146
+
147
+
148
+ """
149
+ HTTP API Response
150
+ """
151
+
152
+
153
+ class ServicePostResponseSer(MyBaseModel):
154
+ status: int
155
+ request: str
156
+
157
+
158
+ class ServiceGetResponseSer(BaseModel):
159
+ status: int
160
+ request: Union[str, dict]
161
+
162
+ # ReCaptcha V3 params
163
+ user_check: str = ""
164
+ user_score: str = ""
165
+
166
+
167
+ class ResponseSer(MyBaseModel):
168
+ captchaSolve: Union[dict, str] = {}
169
+ taskId: Optional[int] = None
170
+ error: bool = False
171
+ errorBody: Optional[str] = None
@@ -112,13 +112,11 @@ class ImageCaptcha(BaseCaptcha):
112
112
  Dict with full server response
113
113
 
114
114
  Notes:
115
- https://rucaptcha.com/api-rucaptcha#solving_funcaptcha_new
115
+ Check class docstirng for more info
116
116
  """
117
117
  # if a local file link is passed
118
118
  if captcha_file:
119
- self.post_payload.update(
120
- {"body": base64.b64encode(self._local_image_captcha(captcha_file)).decode("utf-8")}
121
- )
119
+ self.post_payload.update({"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")})
122
120
  # if the file is transferred in base64 encoding
123
121
  elif captcha_base64:
124
122
  self.post_payload.update({"body": base64.b64encode(captcha_base64).decode("utf-8")})
@@ -133,7 +131,7 @@ class ImageCaptcha(BaseCaptcha):
133
131
 
134
132
  # according to the value of the passed parameter, select the function to save the image
135
133
  if self.save_format == SaveFormatsEnm.CONST.value:
136
- self._image_const_saver(content, self.img_path)
134
+ self._file_const_saver(content, self.img_path)
137
135
  self.post_payload.update({"body": base64.b64encode(content).decode("utf-8")})
138
136
 
139
137
  else:
@@ -183,13 +181,11 @@ class ImageCaptcha(BaseCaptcha):
183
181
  Dict with full server response
184
182
 
185
183
  Notes:
186
- https://rucaptcha.com/api-rucaptcha#solving_funcaptcha_new
184
+ Check class docstirng for more info
187
185
  """
188
186
  # if a local file link is passed
189
187
  if captcha_file:
190
- self.post_payload.update(
191
- {"body": base64.b64encode(self._local_image_captcha(captcha_file)).decode("utf-8")}
192
- )
188
+ self.post_payload.update({"body": base64.b64encode(self._local_file_captcha(captcha_file)).decode("utf-8")})
193
189
  # if the file is transferred in base64 encoding
194
190
  elif captcha_base64:
195
191
  self.post_payload.update({"body": base64.b64encode(captcha_base64).decode("utf-8")})
@@ -204,7 +200,7 @@ class ImageCaptcha(BaseCaptcha):
204
200
 
205
201
  # according to the value of the passed parameter, select the function to save the image
206
202
  if self.save_format == SaveFormatsEnm.CONST.value:
207
- self._image_const_saver(content, self.img_path)
203
+ self._file_const_saver(content, self.img_path)
208
204
  self.post_payload.update({"body": base64.b64encode(content).decode("utf-8")})
209
205
 
210
206
  else:
@@ -1,10 +1,10 @@
1
1
  from .core.base import BaseCaptcha
2
+ from .core.enums import TextCaptchaEnm
2
3
 
3
4
 
4
5
  class TextCaptcha(BaseCaptcha):
5
6
  def __init__(
6
7
  self,
7
- rucaptcha_key: str,
8
8
  language: int = 0,
9
9
  *args,
10
10
  **kwargs,
@@ -45,7 +45,7 @@ class TextCaptcha(BaseCaptcha):
45
45
  https://rucaptcha.com/api-rucaptcha#solving_text_captcha
46
46
  """
47
47
 
48
- super().__init__(rucaptcha_key=rucaptcha_key, *args, **kwargs)
48
+ super().__init__(method=TextCaptchaEnm.TEXT.value, *args, **kwargs)
49
49
 
50
50
  self.post_payload.update({"language": language})
51
51
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-rucaptcha
3
- Version: 5.1.5a0
3
+ Version: 5.2.1
4
4
  Summary: Python 3.7+ RuCaptcha library with AIO module.
5
5
  Home-page: https://andreidrang.github.io/python-rucaptcha/
6
6
  Author: AndreiDrang, redV0ID
@@ -13,6 +13,7 @@ Project-URL: Issue tracker, https://github.com/AndreiDrang/python-rucaptcha/issu
13
13
  Keywords: captcha
14
14
  rucaptcha
15
15
  2captcha
16
+ deathbycaptcha
16
17
  recaptcha
17
18
  geetest
18
19
  hcaptcha
@@ -114,6 +115,8 @@ Is described in the [documentation-website](https://andreidrang.github.io/python
114
115
 
115
116
  ### Changelog
116
117
 
118
+ - v.5.3 - Added support for [Death By Captcha](https://deathbycaptcha.com/) and other services by changing `service_type` and `url_request` \ `url_response` parameters.
119
+ - v.5.2 - Added Audio captcha method.
117
120
  - v.5.1 - Check [releases page](https://github.com/AndreiDrang/python-rucaptcha/releases).
118
121
  - v.5.0 - Added AmazonWAF captcha method.
119
122
  - v.4.2 - Added [Yandex Smart Captcha](https://rucaptcha.com/api-rucaptcha#yandex).
@@ -0,0 +1,29 @@
1
+ python_rucaptcha/SocketAPI.py,sha256=W_ZmEaNTMx--lvaYiLJyckWGt3P-gxoZf0qdxK-onb0,4291
2
+ python_rucaptcha/TikTokCaptcha.py,sha256=PF1ZeV3tb1e2ig3w6kfqqHQzAMOhmGxKjiAyizBRzks,2122
3
+ python_rucaptcha/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ python_rucaptcha/__version__.py,sha256=Wz6OktWKdFA8wb3DR2SbK1ll5ntwA4r6J1py_6STnmA,22
5
+ python_rucaptcha/amazon_waf.py,sha256=On5PdMYuyWp0DLdmnCs04CuYALEx-aKo0P-USsk-dgk,4385
6
+ python_rucaptcha/audio_captcha.py,sha256=4PPViYfpRksZ1CB8ATVUIgC2MtrgrSAMxDqdims7SJQ,7925
7
+ python_rucaptcha/capy_puzzle.py,sha256=wgofttv2qp-7_xHClKE8E6jlV1ca0H4m9fw0ZVnq7RU,7077
8
+ python_rucaptcha/control.py,sha256=xOLHFrXh2OCb-UFYI9Kxcxkj_B4CX2FsLqMHCWjoMHI,13476
9
+ python_rucaptcha/fun_captcha.py,sha256=0IdsCe_4HxSgtdhBb3B6w_LaSel89IUEQYL7_m2D2i4,3859
10
+ python_rucaptcha/gee_test.py,sha256=-mHV8w717UhsmjQUknZgl1elNEXRz-h5GC6S3QtEM6U,10436
11
+ python_rucaptcha/hcaptcha.py,sha256=lvBpIqlZnELEu6aXcAn0hGWybtYvXn0Gmzq4KXO9hPg,3844
12
+ python_rucaptcha/image_captcha.py,sha256=hSLEHV6w-ekthZfdIsIzIM5Wm4QYd3XsDiewb6FRoAM,9281
13
+ python_rucaptcha/key_captcha.py,sha256=qdWlHdLg2RzBGtkBDdGwyfpsLu821Iy2Pk2tIe1p7WI,5022
14
+ python_rucaptcha/lemin_cropped_captcha.py,sha256=m51lbpFk_QHAXUIlaBS7YEkTetZmyKDz0tzHeufz-mU,5209
15
+ python_rucaptcha/re_captcha.py,sha256=9k9_ErZkSge-IrjMAYMbrmkScDD9XAMnny0ko3gNx5I,5437
16
+ python_rucaptcha/rotate_captcha.py,sha256=P5eNM-fLGnW1TVoF2OxlF-Kou-jVPY7yjAvTw5V_Gfc,6187
17
+ python_rucaptcha/text_captcha.py,sha256=Qj32p71KNj02laARIbpAzSszBeuUGTLrSyRoesvubf0,3118
18
+ python_rucaptcha/turnstile.py,sha256=O2Q10oei6pSt47Wdq3lJjCagH_1x2U_h-LcPu0gXaDM,3790
19
+ python_rucaptcha/yandex_smart_captcha.py,sha256=YGBhflfTxWJ603o3COvYAYHSFvbXBb5KrsduBI25YKQ,4695
20
+ python_rucaptcha/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ python_rucaptcha/core/base.py,sha256=UzbJYseQX4nGjGKcjWgAAmXqo9defTd4CURTV1xyrn4,6666
22
+ python_rucaptcha/core/config.py,sha256=SRbH3ENNpVLwqCkXU_cqVILkSqtorUlkYBs-_XMXYzQ,552
23
+ python_rucaptcha/core/enums.py,sha256=QYdnxgALuplSD5dWkW16OP5dMcRCT3BHXPWBKIBFHag,2109
24
+ python_rucaptcha/core/result_handler.py,sha256=OPCpfaItugyUMLJsrNaUZhZAHjb3wzHUOGO02av5KoE,3390
25
+ python_rucaptcha/core/serializer.py,sha256=2C-qTZIDNtH-ENqGORpguOQBnkSbkC1Ka1Ig5aT8Hd8,4617
26
+ python_rucaptcha-5.2.1.dist-info/METADATA,sha256=Rd2bf75L8_HIi2l4ZLtwTuu7Z6s9RvqujOZxYRp5ylA,5660
27
+ python_rucaptcha-5.2.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
28
+ python_rucaptcha-5.2.1.dist-info/top_level.txt,sha256=Eu_atEB79Y7jCsfXPcXF5N8OLt6kKVbvhuRsI1BmSWM,17
29
+ python_rucaptcha-5.2.1.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- python_rucaptcha/SocketAPI.py,sha256=W_ZmEaNTMx--lvaYiLJyckWGt3P-gxoZf0qdxK-onb0,4291
2
- python_rucaptcha/TikTokCaptcha.py,sha256=PF1ZeV3tb1e2ig3w6kfqqHQzAMOhmGxKjiAyizBRzks,2122
3
- python_rucaptcha/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- python_rucaptcha/__version__.py,sha256=nGoohGy6jSVYePb6MpNIuoW_hv5m2Blij1ChyLuHosw,23
5
- python_rucaptcha/amazon_waf.py,sha256=ef1YhPb0Q2BoQSFY1XsLWk-bmeG5ISHu85EOUTGbeYE,4442
6
- python_rucaptcha/capy_puzzle.py,sha256=wgofttv2qp-7_xHClKE8E6jlV1ca0H4m9fw0ZVnq7RU,7077
7
- python_rucaptcha/control.py,sha256=kGKX1DtFGRCoooGJ5lW5U0Xb7w6hjACU9RLOF7qjLOk,12513
8
- python_rucaptcha/fun_captcha.py,sha256=0IdsCe_4HxSgtdhBb3B6w_LaSel89IUEQYL7_m2D2i4,3859
9
- python_rucaptcha/gee_test.py,sha256=-mHV8w717UhsmjQUknZgl1elNEXRz-h5GC6S3QtEM6U,10436
10
- python_rucaptcha/hcaptcha.py,sha256=lvBpIqlZnELEu6aXcAn0hGWybtYvXn0Gmzq4KXO9hPg,3844
11
- python_rucaptcha/image_captcha.py,sha256=TXmJmlBHoqeepVu0NxLrBYj1FoyIdNwAXLPqpwCUT-k,9391
12
- python_rucaptcha/key_captcha.py,sha256=qdWlHdLg2RzBGtkBDdGwyfpsLu821Iy2Pk2tIe1p7WI,5022
13
- python_rucaptcha/lemin_cropped_captcha.py,sha256=m51lbpFk_QHAXUIlaBS7YEkTetZmyKDz0tzHeufz-mU,5209
14
- python_rucaptcha/re_captcha.py,sha256=9k9_ErZkSge-IrjMAYMbrmkScDD9XAMnny0ko3gNx5I,5437
15
- python_rucaptcha/rotate_captcha.py,sha256=P5eNM-fLGnW1TVoF2OxlF-Kou-jVPY7yjAvTw5V_Gfc,6187
16
- python_rucaptcha/text_captcha.py,sha256=HUnhPbN7zvgRAzcXUn88YER-dyXW3UOcM27VV8ZVark,3102
17
- python_rucaptcha/turnstile.py,sha256=O2Q10oei6pSt47Wdq3lJjCagH_1x2U_h-LcPu0gXaDM,3790
18
- python_rucaptcha/yandex_smart_captcha.py,sha256=YGBhflfTxWJ603o3COvYAYHSFvbXBb5KrsduBI25YKQ,4695
19
- python_rucaptcha-5.1.5a0.dist-info/METADATA,sha256=6JPv2UXW3JV7mZGKTTfCqJN0lFYzWvE2a6vWBlS7Yh4,5436
20
- python_rucaptcha-5.1.5a0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
21
- python_rucaptcha-5.1.5a0.dist-info/top_level.txt,sha256=Eu_atEB79Y7jCsfXPcXF5N8OLt6kKVbvhuRsI1BmSWM,17
22
- python_rucaptcha-5.1.5a0.dist-info/RECORD,,