tweepy-self 1.6.3__py3-none-any.whl → 1.10.0b9__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,303 @@
1
+ Metadata-Version: 2.1
2
+ Name: tweepy-self
3
+ Version: 1.10.0b9
4
+ Summary: Twitter (selfbot) for Python!
5
+ Home-page: https://github.com/alenkimov/tweepy-self
6
+ Author: Alen
7
+ Author-email: alen.kimov@gmail.com
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: aiohttp (>=3.9,<4.0)
13
+ Requires-Dist: beautifulsoup4 (>=4,<5)
14
+ Requires-Dist: better-proxy (>=1.1,<2.0)
15
+ Requires-Dist: curl_cffi (==0.6.2)
16
+ Requires-Dist: loguru (>=0.7,<0.8)
17
+ Requires-Dist: lxml (>=5,<6)
18
+ Requires-Dist: pydantic (>=2,<3)
19
+ Requires-Dist: pyotp (>=2,<3)
20
+ Requires-Dist: requests (>=2,<3)
21
+ Requires-Dist: tenacity (>=8,<9)
22
+ Requires-Dist: yarl (>=1,<2)
23
+ Project-URL: Repository, https://github.com/alenkimov/tweepy-self
24
+ Project-URL: Source, https://github.com/alenkimov/tweepy-self
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Tweepy-self
28
+ [![Telegram channel](https://img.shields.io/endpoint?url=https://runkit.io/damiankrawczyk/telegram-badge/branches/master?url=https://t.me/cum_insider)](https://t.me/cum_insider)
29
+ [![PyPI version info](https://img.shields.io/pypi/v/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
30
+ [![PyPI supported Python versions](https://img.shields.io/pypi/pyversions/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
31
+ [![PyPI downloads per month](https://img.shields.io/pypi/dm/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
32
+
33
+ A modern, easy to use, feature-rich, and async ready API wrapper for Twitter's user API written in Python.
34
+
35
+ More libraries of the family:
36
+ - [better-proxy](https://github.com/alenkimov/better_proxy)
37
+ - [better-web3](https://github.com/alenkimov/better_web3)
38
+
39
+ Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
40
+
41
+ ## Key Features
42
+ - Modern Pythonic API using async and await.
43
+ - Prevents user account automation detection.
44
+
45
+ ## Installing
46
+ ```bash
47
+ pip install tweepy-self
48
+ ```
49
+
50
+ ## Example
51
+ ```python
52
+ import asyncio
53
+ import twitter
54
+
55
+ twitter_account = twitter.Account(auth_token="auth_token")
56
+
57
+ async def main():
58
+ async with twitter.Client(twitter_account) as twitter_client:
59
+ print(f"Logged in as @{twitter_account.username} (id={twitter_account.id})")
60
+ tweet = await twitter_client.tweet("Hello tweepy-self! <3")
61
+ print(tweet)
62
+
63
+ if __name__ == "__main__":
64
+ asyncio.run(main())
65
+ ```
66
+
67
+ ## Документация
68
+ ### Некоторые истины
69
+ Имена пользователей нужно передавать БЕЗ знака `@`.
70
+ Чтобы наверняка убрать этот знак можно передать имя пользователя в функцию `twitter.utils.remove_at_sign()`
71
+
72
+ Automating user accounts is against the Twitter ToS. This library is a proof of concept and I cannot recommend using it. Do so at your own risk
73
+
74
+ ### Как включить логирование
75
+ ```python
76
+ import sys
77
+ from loguru import logger
78
+
79
+ logger.remove()
80
+ logger.add(sys.stdout, level="INFO")
81
+ logger.enable("twitter")
82
+ ```
83
+
84
+ `level="DEBUG"` позволяет увидеть информацию обо всех запросах.
85
+
86
+ ### Аккаунт
87
+ После любого взаимодействия с Twitter устанавливается статус аккаунта:
88
+ - `UNKNOWN` - Статус аккаунта не установлен. Это статус по умолчанию.
89
+ - `BAD_TOKEN` - Неверный или мертвый токен.
90
+ - `SUSPENDED` - Действие учетной записи приостановлено. Тем не менее возможен запрос данных, а также авторизация через OAuth и OAuth2.
91
+ - `LOCKED` - Учетная запись заморожена (лок). Для разморозки (анлок) требуется прохождение капчи (funcaptcha).
92
+ - `CONSENT_LOCKED` - Учетная запись заморожена (лок). Условия для разморозки неизвестны.
93
+ - `GOOD` - Аккаунт в порядке.
94
+
95
+ Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
96
+ Например, простой запрос данных об аккаунте честно вернет данные, даже если действие вашей учетной записи приостановлено.
97
+
98
+ Для достоверной установки статуса аккаунта используйте метод `Client.establish_status()`
99
+
100
+ ### Настройка клиента
101
+ Класс `twitter.Client` может быть сконфигурирован перед работой. Он принимает в себя следующие параметры:
102
+ - `wait_on_rate_limit` Если включено, то при достижении Rate Limit будет ждать, вместо того, чтобы выбрасывать исключение. Включено по умолчанию.
103
+ - `capsolver_api_key` API ключ сервиса [CapSolver](https://dashboard.capsolver.com/passport/register?inviteCode=m-aE3NeBGZLU). Нужен для автоматической разморозки аккаунта.
104
+ - `max_unlock_attempts` Максимальное количество попыток разморозки аккаунта. По умолчанию: 5.
105
+ - `auto_relogin` Если включено, то при невалидном токене (`BAD_TOKEN`) и предоставленных данных для авторизации (имя пользователя, пароль и totp_secret) будет произведен автоматический релогин (замена токена). Включено по умолчанию.
106
+ - `update_account_info_on_startup` Если включено, то на старте будет автоматически запрошена информация об аккаунте. Включено по умолчанию.
107
+ - `**session_kwargs` Любые параметры, которые может принимать сессия `curl_cffi.requests.AsyncSession`. Например, можно передать параметр `proxy`.
108
+
109
+ Пример настройки клиента:
110
+ ```python
111
+ async with twitter.Client(
112
+ twitter_account,
113
+ capsolver_api_key="CAP-00000000000000000000000000000000",
114
+ proxy="http://login:password@ip:port", # Можно передавать в любом формате, так как используется библиотека better_proxy
115
+ ) as twitter_client:
116
+ ...
117
+ ```
118
+
119
+ ### Доступные методы
120
+ Список всех методов.
121
+
122
+ #### Запрос информации о собственном аккаунте
123
+ ```python
124
+ twitter_client.update_account_info()
125
+ print(twitter_client.account)
126
+ ```
127
+
128
+ #### Запрос пользователя по username или по ID
129
+
130
+ ```python
131
+ bro = twitter_client.request_user_by_username(bro_username)
132
+ bro = twitter_client.request_user_by_id(bro_id)
133
+ bros = twitter_client.request_users_by_ids([bro1_id, bro2_id, ...])
134
+ ```
135
+
136
+ #### Загрузка изображения на сервер, смена аватарки и баннера
137
+ ```python
138
+ image = open("image.png", "rb").read()
139
+ media = await twitter_client.upload_image(image)
140
+ avatar_image_url = await twitter_client.update_profile_avatar(media.id)
141
+ banner_image_url = await twitter_client.update_profile_banner(media.id)
142
+ ```
143
+
144
+ #### Изменения данных профиля
145
+ ```python
146
+ await twitter_client.update_birthdate(day=1, month=12, year=2000)
147
+ await twitter_client.update_profile( # Locks account!
148
+ name="New Name",
149
+ description="New description",
150
+ location="New York",
151
+ website="https://github.com/alenkimov/tweepy-self",
152
+ )
153
+ ```
154
+
155
+ #### Включение TOTP (2FA)
156
+ ```python
157
+ if await twitter_client.totp_is_enabled():
158
+ print(f"TOTP уже включен.")
159
+ return
160
+
161
+ await twitter_client.enable_totp()
162
+ ```
163
+
164
+ #### Логин, если включен TOTP (2FA)
165
+ ```python
166
+ import twitter
167
+
168
+ twitter_account = twitter.Account(auth_token="...", username="...", password="...", totp_secret="...")
169
+ await twitter_client.login()
170
+ print(f"Logged in! New auth_token: {twitter_account.auth_token}")
171
+ ```
172
+
173
+ #### Смена имени пользователя и пароля
174
+ ```python
175
+ twitter_account = twitter.Account("auth_token", password="password")
176
+ ...
177
+ await twitter_client.change_username("new_username")
178
+ await twitter_client.request_user()
179
+ print(f"New username: {twitter_account.username}")
180
+
181
+ await twitter_client.change_password("new_password")
182
+ print(f"New password: {twitter_account.password}")
183
+ print(f"New auth_token: {twitter_account.auth_token}")
184
+ ```
185
+
186
+ #### Авторизация с OAuth
187
+ ```python
188
+ auth_code = await twitter_client.oauth(oauth_token, **oauth_params)
189
+ ```
190
+
191
+ #### Авторизация с OAuth2
192
+ ```python
193
+ # Изучите запросы сервиса и найдите подобные данные для авторизации (привязки):
194
+ oauth2_data = {
195
+ 'response_type': 'code',
196
+ 'client_id': 'TjFVQm52ZDFGWEtNT0tKaktaSWU6MTpjaQ',
197
+ 'redirect_uri': 'https://waitlist.lens.xyz/tw/',
198
+ 'scope': 'users.read tweet.read offline.access',
199
+ 'state': 'state', # Может быть как статичным, так и динамическим.
200
+ 'code_challenge': 'challenge',
201
+ 'code_challenge_method': 'plain'
202
+ }
203
+
204
+ auth_code = await twitter_client.oauth2(**oauth2_data)
205
+ # Передайте код авторизации (привязки) сервису.
206
+ # Сервис также может потребовать state, если он динамический.
207
+ ```
208
+
209
+ #### Отправка сообщения:
210
+ ```python
211
+ bro = await twitter_client.request_user("bro_username")
212
+ await twitter_client.send_message(bro.id, "I love you!")
213
+ ```
214
+
215
+ #### Запрос входящих сообщений:
216
+ ```python
217
+ messages = await twitter_client.request_messages()
218
+ for message in messages:
219
+ message_data = message["message_data"]
220
+ recipient_id = message_data["recipient_id"]
221
+ sender_id = message_data["sender_id"]
222
+ text = message_data["text"]
223
+ print(f"[id {sender_id}] -> [id {recipient_id}]: {text}")
224
+ ```
225
+
226
+ Так как мне почти не приходилось работать с сообщениями, я еще не сделал для этого удобных моделей.
227
+ Поэтому приходится работать со словарем.
228
+
229
+ #### Пост (твит)
230
+ ```python
231
+ tweet = await twitter_client.tweet("I love you tweepy-self! <3")
232
+ print(f"Любовь выражена! Tweet id: {tweet.id}")
233
+ ```
234
+
235
+ #### Лайк, репост (ретвит), коммент (реплай)
236
+ ```python
237
+ # Лайк
238
+ print(f"Tweet {tweet_id} is liked: {await twitter_client.like(tweet_id)}")
239
+
240
+ # Репост (ретвит)
241
+ print(f"Tweet {tweet_id} is retweeted. Tweet id: {await twitter_client.repost(tweet_id)}")
242
+
243
+ # Коммент (реплай)
244
+ print(f"Tweet {tweet_id} is replied. Reply id: {await twitter_client.reply(tweet_id, 'tem razão')}")
245
+ ```
246
+
247
+ #### Цитата
248
+ ```python
249
+ tweet_url = 'https://twitter.com/CreamIce_Cone/status/1691735090529976489'
250
+ # Цитата (Quote tweet)
251
+ quote_tweet_id = await twitter_client.quote(tweet_url, 'oh....')
252
+ print(f"Quoted! Tweet id: {quote_tweet_id}")
253
+ ```
254
+
255
+ #### Подписка и отписка
256
+ ```python
257
+ # Подписываемся на Илона Маска
258
+ print(f"@{elonmusk.username} is followed: {await twitter_client.follow(elonmusk.id)}")
259
+
260
+ # Отписываемся от Илона Маска
261
+ print(f"@{elonmusk.username} is unfollowed: {await twitter_client.unfollow(elonmusk.id)}")
262
+ ```
263
+
264
+ #### Закрепление твита
265
+ ```python
266
+ pinned = await twitter_client.pin_tweet(tweet_id)
267
+ print(f"Tweet is pined: {pinned}")
268
+ ```
269
+
270
+ #### Запрос своих и чужих подписчиков
271
+ ```python
272
+
273
+ followers = await twitter_client.request_followers()
274
+ print("Твои подписчики:")
275
+ for user in followers:
276
+ print(user)
277
+
278
+ followings = await twitter_client.request_followings()
279
+ print(f"Ты подписан на:")
280
+ for user in followings:
281
+ print(user)
282
+
283
+ bro_followers = await twitter_client.request_followers(bro_id)
284
+ print(f"Подписчики твоего бро (id={bro_id}):")
285
+ for user in bro_followers:
286
+ print(user)
287
+
288
+ bro_followings = await twitter_client.request_followings(bro_id)
289
+ print(f"На твоего бро (id={bro_id}) подписаны:")
290
+ for user in bro_followings:
291
+ print(user)
292
+ ```
293
+
294
+ #### Голосование
295
+ ```python
296
+ vote_data = await twitter_client.vote(tweet_id, card_id, choice_number)
297
+ votes_count = vote_data["card"]["binding_values"]["choice1_count"]["string_value"]
298
+ print(f"Votes: {votes_count}")
299
+ ```
300
+
301
+ Так как мне почти не приходилось работать с голосованиями, я еще не сделал для этого удобных моделей.
302
+ Поэтому приходится работать со словарем.
303
+
@@ -0,0 +1,23 @@
1
+ twitter/__init__.py,sha256=-CmcPdm1z-OkG8LkJVe75PwdYKBqBfMpD9WdoXcnGuc,732
2
+ twitter/_capsolver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ twitter/_capsolver/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ twitter/_capsolver/core/base.py,sha256=In3qDLgRh1z1UZLaLFgYcDEdnqW3d62PVzgEjU2S4BU,8883
5
+ twitter/_capsolver/core/config.py,sha256=8_eXT6N2hBheN2uCMNhqk8tLZRJjLDTYLK208fqIkhM,1054
6
+ twitter/_capsolver/core/enum.py,sha256=ivfAEN6jrg3iaq5C3H7CuRqsvOloX1b8lF8cLa3zaiY,1741
7
+ twitter/_capsolver/core/serializer.py,sha256=xPEUIPgytuw2wM1ubTY3RMhJGVyp_d3bokPTx0BjF0c,2602
8
+ twitter/_capsolver/fun_captcha.py,sha256=VVbTmn08cGnvPMGdJmPxaLfAIPxyA68oTSAyEL8RWnU,10974
9
+ twitter/account.py,sha256=joAB5Zw-Le5E3kOZ-1nb4DPGlTqWYv2Vs6gJ3cwu7is,3175
10
+ twitter/base/__init__.py,sha256=Q2ko0HeOS5tiBnDVKxxaZYetwRR3YXJ67ujL3oThGd4,141
11
+ twitter/base/client.py,sha256=J_iL4ZGfwTbZ2gpjtFCbBxNgt7weJ55EeMGzYsLtjf4,500
12
+ twitter/base/session.py,sha256=JFPS-9Qae1iY3NfNcywxvWWmRDijaU_Rjs3WaQ00iFA,2071
13
+ twitter/client.py,sha256=VZ9cymxz1KMSaqVTTFfCfuC1c5xZHdRUOD0XENSf53w,75555
14
+ twitter/enums.py,sha256=-OH6Ibxarq5qt4E2AhkProVawcEyIf5YG_h_G5xiV9Y,270
15
+ twitter/errors.py,sha256=oNa0Neos80ZK4-0FBzqgxXonH564qFnoN-kavHalfR4,5274
16
+ twitter/models.py,sha256=CrGb3dvA0U4PfPTkUtuprPKXpqkLpM8AR_-De4D3efM,5677
17
+ twitter/utils/__init__.py,sha256=usxpfcRQ7zxTTgZ-i425tT7hIz73Pwh9FDj4t6O3dYg,663
18
+ twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
19
+ twitter/utils/html.py,sha256=nrOJw0vUKfBaHgFaQSQIdXfvfZ8mdu84MU_s46kJTJ4,2087
20
+ twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
21
+ tweepy_self-1.10.0b9.dist-info/METADATA,sha256=DUbx5Sw46Hy6B1ZIsQVOnFx3BTjH1qZ0yplMDTJ7njE,13153
22
+ tweepy_self-1.10.0b9.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
23
+ tweepy_self-1.10.0b9.dist-info/RECORD,,
twitter/__init__.py CHANGED
@@ -6,14 +6,23 @@ A basic wrapper for the Twitter user API.
6
6
  """
7
7
 
8
8
  from .client import Client
9
- from .account import Account, AccountStatus, load_accounts_from_file, extract_accounts_to_file
10
- from .models import Tweet, UserData
9
+ from .account import (
10
+ Account,
11
+ AccountStatus,
12
+ load_accounts_from_file,
13
+ extract_accounts_to_file,
14
+ )
15
+ from .models import Tweet, User, Media, Image
11
16
  from . import errors, utils
12
17
 
13
18
  __all__ = [
14
19
  "Client",
15
20
  "Account",
16
21
  "AccountStatus",
22
+ "Tweet",
23
+ "User",
24
+ "Media",
25
+ "Image",
17
26
  "utils",
18
27
  "errors",
19
28
  "load_accounts_from_file",
@@ -22,9 +31,10 @@ __all__ = [
22
31
 
23
32
 
24
33
  import warnings
34
+
25
35
  # HACK: Ignore event loop warnings from curl_cffi
26
- warnings.filterwarnings('ignore', module='curl_cffi')
36
+ warnings.filterwarnings("ignore", module="curl_cffi")
27
37
 
38
+ from loguru import logger
28
39
 
29
- from python3_capsolver.core import config
30
- config.APP_ID = "6F895B2F-F454-44D1-8FE0-77ACAD3DBDC8"
40
+ logger.disable("twitter")
File without changes
File without changes
@@ -0,0 +1,227 @@
1
+ import time
2
+ import asyncio
3
+ import logging
4
+ from typing import Any, Dict, Type
5
+ from urllib import parse
6
+
7
+ import aiohttp
8
+ import requests
9
+ from pydantic import BaseModel
10
+ from requests.adapters import HTTPAdapter
11
+
12
+ from .enum import ResponseStatusEnm, EndpointPostfixEnm
13
+ from .config import RETRIES, REQUEST_URL, VALID_STATUS_CODES, attempts_generator
14
+ from .serializer import (
15
+ CaptchaOptionsSer,
16
+ CaptchaResponseSer,
17
+ RequestCreateTaskSer,
18
+ RequestGetTaskResultSer,
19
+ )
20
+
21
+
22
+ class BaseCaptcha:
23
+ """
24
+ Basic Captcha solving class
25
+
26
+ Args:
27
+ api_key: Capsolver API key
28
+ captcha_type: Captcha type name, like `ReCaptchaV2Task` and etc.
29
+ sleep_time: The waiting time between requests to get the result of the Captcha
30
+ request_url: API address for sending requests
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ api_key: str,
36
+ sleep_time: int = 5,
37
+ request_url: str = REQUEST_URL,
38
+ **kwargs,
39
+ ):
40
+ # assign args to validator
41
+ self.__params = CaptchaOptionsSer(**locals())
42
+ self.__request_url = request_url
43
+
44
+ # prepare session
45
+ self.__session = requests.Session()
46
+ self.__session.mount("http://", HTTPAdapter(max_retries=RETRIES))
47
+ self.__session.mount("https://", HTTPAdapter(max_retries=RETRIES))
48
+
49
+ def _prepare_create_task_payload(self, serializer: Type[BaseModel], create_params: Dict[str, Any] = None) -> None:
50
+ """
51
+ Method prepare `createTask` payload
52
+
53
+ Args:
54
+ serializer: Serializer for task creation
55
+ create_params: Parameters for task creation payload
56
+
57
+ Examples:
58
+
59
+ >>> self._prepare_create_task_payload(serializer=PostRequestSer, create_params={})
60
+
61
+ """
62
+ self.task_payload = serializer(clientKey=self.__params.api_key)
63
+ # added task params to payload
64
+ self.task_payload.task = {**create_params} if create_params else {}
65
+
66
+ def __enter__(self):
67
+ return self
68
+
69
+ def __exit__(self, exc_type, exc_value, traceback):
70
+ if exc_type:
71
+ return False
72
+ return True
73
+
74
+ async def __aenter__(self):
75
+ return self
76
+
77
+ async def __aexit__(self, exc_type, exc_value, traceback):
78
+ if exc_type:
79
+ return False
80
+ return True
81
+
82
+ """
83
+ Sync part
84
+ """
85
+
86
+ def _processing_captcha(
87
+ self, create_params: dict, serializer: Type[BaseModel] = RequestCreateTaskSer
88
+ ) -> CaptchaResponseSer:
89
+ self._prepare_create_task_payload(serializer=serializer, create_params=create_params)
90
+ self.created_task_data = CaptchaResponseSer(**self._create_task())
91
+
92
+ # if task created and ready - return result
93
+ if self.created_task_data.status == ResponseStatusEnm.Ready.value:
94
+ return self.created_task_data
95
+ # if captcha is not ready but task success created - waiting captcha result
96
+ elif self.created_task_data.errorId == 0:
97
+ return self._get_result()
98
+ return self.created_task_data
99
+
100
+ def _create_task(self, url_postfix: str = EndpointPostfixEnm.CREATE_TASK.value) -> dict:
101
+ """
102
+ Function send SYNC request to service and wait for result
103
+ """
104
+ try:
105
+ resp = self.__session.post(
106
+ parse.urljoin(self.__request_url, url_postfix), json=self.task_payload.dict(exclude_none=True)
107
+ )
108
+ if resp.status_code in VALID_STATUS_CODES:
109
+ return resp.json()
110
+ else:
111
+ raise ValueError(resp.raise_for_status())
112
+ except Exception as error:
113
+ logging.exception(error)
114
+ raise
115
+
116
+ def _get_result(self, url_postfix: str = EndpointPostfixEnm.GET_TASK_RESULT.value) -> CaptchaResponseSer:
117
+ """
118
+ Method send SYNC request to service and wait for result
119
+ """
120
+ # initial waiting
121
+ time.sleep(self.__params.sleep_time)
122
+
123
+ get_result_payload = RequestGetTaskResultSer(
124
+ clientKey=self.__params.api_key, taskId=self.created_task_data.taskId
125
+ )
126
+ attempts = attempts_generator()
127
+ for _ in attempts:
128
+ try:
129
+ resp = self.__session.post(
130
+ parse.urljoin(self.__request_url, url_postfix), json=get_result_payload.dict(exclude_none=True)
131
+ )
132
+ if resp.status_code in VALID_STATUS_CODES:
133
+ result_data = CaptchaResponseSer(**resp.json())
134
+ if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
135
+ # if captcha ready\failed or have unknown status - return exist data
136
+ return result_data
137
+ else:
138
+ raise ValueError(resp.raise_for_status())
139
+ except Exception as error:
140
+ logging.exception(error)
141
+ raise
142
+
143
+ # if captcha just created or in processing now - wait
144
+ time.sleep(self.__params.sleep_time)
145
+ # default response if server is silent
146
+ return CaptchaResponseSer(
147
+ errorId=1,
148
+ errorCode="ERROR_CAPTCHA_UNSOLVABLE",
149
+ errorDescription="Captcha not recognized",
150
+ taskId=self.created_task_data.taskId,
151
+ status=ResponseStatusEnm.Failed,
152
+ )
153
+
154
+ """
155
+ Async part
156
+ """
157
+
158
+ async def _aio_processing_captcha(
159
+ self, create_params: dict, serializer: Type[BaseModel] = RequestCreateTaskSer
160
+ ) -> CaptchaResponseSer:
161
+ self._prepare_create_task_payload(serializer=serializer, create_params=create_params)
162
+ self.created_task_data = CaptchaResponseSer(**await self._aio_create_task())
163
+
164
+ # if task created and already ready - return result
165
+ if self.created_task_data.status == ResponseStatusEnm.Ready.value:
166
+ return self.created_task_data
167
+ # if captcha is not ready but task success created - waiting captcha result
168
+ elif self.created_task_data.errorId == 0:
169
+ return await self._aio_get_result()
170
+ return self.created_task_data
171
+
172
+ async def _aio_create_task(self, url_postfix: str = EndpointPostfixEnm.CREATE_TASK.value) -> dict:
173
+ """
174
+ Function send the ASYNC request to service and wait for result
175
+ """
176
+ async with aiohttp.ClientSession() as session:
177
+ try:
178
+ async with session.post(
179
+ parse.urljoin(self.__request_url, url_postfix), json=self.task_payload.dict(exclude_none=True)
180
+ ) as resp:
181
+ if resp.status in VALID_STATUS_CODES:
182
+ return await resp.json()
183
+ else:
184
+ raise ValueError(resp.reason)
185
+ except Exception as error:
186
+ logging.exception(error)
187
+ raise
188
+
189
+ async def _aio_get_result(self, url_postfix: str = EndpointPostfixEnm.GET_TASK_RESULT.value) -> CaptchaResponseSer:
190
+ """
191
+ Function send the ASYNC request to service and wait for result
192
+ """
193
+ # initial waiting
194
+ await asyncio.sleep(self.__params.sleep_time)
195
+
196
+ get_result_payload = RequestGetTaskResultSer(
197
+ clientKey=self.__params.api_key, taskId=self.created_task_data.taskId
198
+ )
199
+ attempts = attempts_generator()
200
+ async with aiohttp.ClientSession() as session:
201
+ for _ in attempts:
202
+ try:
203
+ async with session.post(
204
+ parse.urljoin(self.__request_url, url_postfix), json=get_result_payload.dict(exclude_none=True)
205
+ ) as resp:
206
+ if resp.status in VALID_STATUS_CODES:
207
+ result_data = CaptchaResponseSer(**await resp.json())
208
+ if result_data.status in (ResponseStatusEnm.Ready, ResponseStatusEnm.Failed):
209
+ # if captcha ready\failed or have unknown status - return exist data
210
+ return result_data
211
+ else:
212
+ raise ValueError(resp.reason)
213
+ except Exception as error:
214
+ logging.exception(error)
215
+ raise
216
+
217
+ # if captcha just created or in processing now - wait
218
+ await asyncio.sleep(self.__params.sleep_time)
219
+
220
+ # default response if server is silent
221
+ return CaptchaResponseSer(
222
+ errorId=1,
223
+ errorCode="ERROR_CAPTCHA_UNSOLVABLE",
224
+ errorDescription="Captcha not recognized",
225
+ taskId=self.created_task_data.taskId,
226
+ status=ResponseStatusEnm.Failed,
227
+ )
@@ -0,0 +1,36 @@
1
+ from typing import Generator
2
+
3
+ from tenacity import AsyncRetrying, wait_fixed, stop_after_attempt
4
+ from requests.adapters import Retry
5
+
6
+ RETRIES = Retry(total=5, backoff_factor=0.9, status_forcelist=[500, 502, 503, 504])
7
+ ASYNC_RETRIES = AsyncRetrying(wait=wait_fixed(5), stop=stop_after_attempt(5), reraise=True)
8
+
9
+ REQUEST_URL = "https://api.capsolver.com"
10
+ VALID_STATUS_CODES = (200, 202, 400, 401, 405)
11
+
12
+ APP_ID = "6F895B2F-F454-44D1-8FE0-77ACAD3DBDC8"
13
+
14
+
15
+ # Connection retry generator
16
+ def attempts_generator(amount: int = 16) -> Generator:
17
+ """
18
+ Function generates a generator of length equal to `amount`
19
+
20
+ Args:
21
+ amount: number of attempts generated
22
+
23
+ Yields:
24
+ int: The next number in the range of 1 to ``amount`` - 1.
25
+
26
+ Examples:
27
+ Examples should be written in doctest format, and should illustrate how
28
+ to use the function.
29
+
30
+ >>> print([i for i in attempts_generator(5)])
31
+ [1, 2, 3, 4]
32
+
33
+ Returns:
34
+ Attempt number
35
+ """
36
+ yield from range(1, amount)