python-rucaptcha 5.1.5a0__tar.gz → 5.2.1__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-5.1.5a0 → python-rucaptcha-5.2.1}/PKG-INFO +4 -1
- python-rucaptcha-5.2.1/python_rucaptcha/__version__.py +1 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/amazon_waf.py +1 -2
- python-rucaptcha-5.2.1/python_rucaptcha/audio_captcha.py +208 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/control.py +26 -1
- python-rucaptcha-5.2.1/python_rucaptcha/core/__init__.py +0 -0
- python-rucaptcha-5.2.1/python_rucaptcha/core/base.py +191 -0
- python-rucaptcha-5.2.1/python_rucaptcha/core/config.py +21 -0
- python-rucaptcha-5.2.1/python_rucaptcha/core/enums.py +112 -0
- python-rucaptcha-5.2.1/python_rucaptcha/core/result_handler.py +92 -0
- python-rucaptcha-5.2.1/python_rucaptcha/core/serializer.py +171 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/image_captcha.py +6 -10
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/text_captcha.py +2 -2
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/PKG-INFO +4 -1
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/SOURCES.txt +8 -1
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/setup.py +6 -8
- python-rucaptcha-5.1.5a0/python_rucaptcha/__version__.py +0 -1
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/SocketAPI.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/TikTokCaptcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/__init__.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/capy_puzzle.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/fun_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/gee_test.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/hcaptcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/key_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/lemin_cropped_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/re_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/rotate_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/turnstile.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/yandex_smart_captcha.py +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/dependency_links.txt +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/not-zip-safe +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/requires.txt +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/top_level.txt +0 -0
- {python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-rucaptcha
|
|
3
|
-
Version: 5.1
|
|
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
|
|
@@ -109,6 +110,8 @@ Is described in the [documentation-website](https://andreidrang.github.io/python
|
|
|
109
110
|
|
|
110
111
|
### Changelog
|
|
111
112
|
|
|
113
|
+
- 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.
|
|
114
|
+
- v.5.2 - Added Audio captcha method.
|
|
112
115
|
- v.5.1 - Check [releases page](https://github.com/AndreiDrang/python-rucaptcha/releases).
|
|
113
116
|
- v.5.0 - Added AmazonWAF captcha method.
|
|
114
117
|
- v.4.2 - Added [Yandex Smart Captcha](https://rucaptcha.com/api-rucaptcha#yandex).
|
|
@@ -0,0 +1 @@
|
|
|
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__(
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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__(
|
|
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
|
|
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
|
|
@@ -109,6 +110,8 @@ Is described in the [documentation-website](https://andreidrang.github.io/python
|
|
|
109
110
|
|
|
110
111
|
### Changelog
|
|
111
112
|
|
|
113
|
+
- 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.
|
|
114
|
+
- v.5.2 - Added Audio captcha method.
|
|
112
115
|
- v.5.1 - Check [releases page](https://github.com/AndreiDrang/python-rucaptcha/releases).
|
|
113
116
|
- v.5.0 - Added AmazonWAF captcha method.
|
|
114
117
|
- v.4.2 - Added [Yandex Smart Captcha](https://rucaptcha.com/api-rucaptcha#yandex).
|
|
@@ -4,6 +4,7 @@ python_rucaptcha/TikTokCaptcha.py
|
|
|
4
4
|
python_rucaptcha/__init__.py
|
|
5
5
|
python_rucaptcha/__version__.py
|
|
6
6
|
python_rucaptcha/amazon_waf.py
|
|
7
|
+
python_rucaptcha/audio_captcha.py
|
|
7
8
|
python_rucaptcha/capy_puzzle.py
|
|
8
9
|
python_rucaptcha/control.py
|
|
9
10
|
python_rucaptcha/fun_captcha.py
|
|
@@ -22,4 +23,10 @@ python_rucaptcha.egg-info/SOURCES.txt
|
|
|
22
23
|
python_rucaptcha.egg-info/dependency_links.txt
|
|
23
24
|
python_rucaptcha.egg-info/not-zip-safe
|
|
24
25
|
python_rucaptcha.egg-info/requires.txt
|
|
25
|
-
python_rucaptcha.egg-info/top_level.txt
|
|
26
|
+
python_rucaptcha.egg-info/top_level.txt
|
|
27
|
+
python_rucaptcha/core/__init__.py
|
|
28
|
+
python_rucaptcha/core/base.py
|
|
29
|
+
python_rucaptcha/core/config.py
|
|
30
|
+
python_rucaptcha/core/enums.py
|
|
31
|
+
python_rucaptcha/core/result_handler.py
|
|
32
|
+
python_rucaptcha/core/serializer.py
|
|
@@ -50,11 +50,8 @@ class UploadCommand(Command):
|
|
|
50
50
|
pass
|
|
51
51
|
|
|
52
52
|
def run(self):
|
|
53
|
-
logging.info("Clean builds . . .")
|
|
54
|
-
shutil.rmtree("dist/", ignore_errors=True)
|
|
55
|
-
|
|
56
53
|
logging.info("Building Source and Wheel distribution . . .")
|
|
57
|
-
os.system("python setup.py sdist")
|
|
54
|
+
os.system("python setup.py sdist bdist_wheel")
|
|
58
55
|
|
|
59
56
|
logging.info("Uploading the package to PyPI via Twin . . .")
|
|
60
57
|
os.system("twine upload dist/* --verbose")
|
|
@@ -62,13 +59,13 @@ class UploadCommand(Command):
|
|
|
62
59
|
logging.info("🤖 Uploaded . . .")
|
|
63
60
|
|
|
64
61
|
logging.info("Clean dist . . .")
|
|
65
|
-
shutil.rmtree("dist/")
|
|
62
|
+
shutil.rmtree("dist/", ignore_errors=True)
|
|
66
63
|
|
|
67
64
|
logging.info("Clean build . . .")
|
|
68
|
-
shutil.rmtree("build/")
|
|
65
|
+
shutil.rmtree("build/", ignore_errors=True)
|
|
69
66
|
|
|
70
67
|
logging.info("Clean python_rucaptcha.egg-info . . .")
|
|
71
|
-
shutil.rmtree("python_rucaptcha.egg-info/")
|
|
68
|
+
shutil.rmtree("python_rucaptcha.egg-info/", ignore_errors=True)
|
|
72
69
|
sys.exit()
|
|
73
70
|
|
|
74
71
|
|
|
@@ -76,7 +73,7 @@ setup(
|
|
|
76
73
|
name=NAME,
|
|
77
74
|
version=VERSION,
|
|
78
75
|
author=AUTHOR,
|
|
79
|
-
packages=["python_rucaptcha"],
|
|
76
|
+
packages=["python_rucaptcha", "python_rucaptcha.core"],
|
|
80
77
|
install_requires=REQUIRED,
|
|
81
78
|
description=DESCRIPTION,
|
|
82
79
|
long_description=long_description,
|
|
@@ -97,6 +94,7 @@ setup(
|
|
|
97
94
|
captcha
|
|
98
95
|
rucaptcha
|
|
99
96
|
2captcha
|
|
97
|
+
deathbycaptcha
|
|
100
98
|
recaptcha
|
|
101
99
|
geetest
|
|
102
100
|
hcaptcha
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "5.1.5a"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/lemin_cropped_captcha.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha/yandex_smart_captcha.py
RENAMED
|
File without changes
|
{python-rucaptcha-5.1.5a0 → python-rucaptcha-5.2.1}/python_rucaptcha.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|