tweepy-self 1.6.3__py3-none-any.whl → 1.10.0b9__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.
@@ -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)