tweepy-self 1.10.0b1__py3-none-any.whl → 1.10.0b4__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.0b4
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 (2F)
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
+
@@ -10,14 +10,14 @@ twitter/account.py,sha256=joAB5Zw-Le5E3kOZ-1nb4DPGlTqWYv2Vs6gJ3cwu7is,3175
10
10
  twitter/base/__init__.py,sha256=Q2ko0HeOS5tiBnDVKxxaZYetwRR3YXJ67ujL3oThGd4,141
11
11
  twitter/base/client.py,sha256=J_iL4ZGfwTbZ2gpjtFCbBxNgt7weJ55EeMGzYsLtjf4,500
12
12
  twitter/base/session.py,sha256=JFPS-9Qae1iY3NfNcywxvWWmRDijaU_Rjs3WaQ00iFA,2071
13
- twitter/client.py,sha256=CySQ-hTFiPGFKhPBNw4nn_xnO5hdpjmXK90QpSEzRG4,66878
13
+ twitter/client.py,sha256=PNAco0kak2q3E7iinSQePa7LKAMcxgbbgH0NtIo8-Jk,69923
14
14
  twitter/enums.py,sha256=-OH6Ibxarq5qt4E2AhkProVawcEyIf5YG_h_G5xiV9Y,270
15
15
  twitter/errors.py,sha256=oNa0Neos80ZK4-0FBzqgxXonH564qFnoN-kavHalfR4,5274
16
16
  twitter/models.py,sha256=7yObMPUUEwJEbraHzFwmUKd91UhR2-zyfJTm4xIqrSQ,4834
17
17
  twitter/utils/__init__.py,sha256=usxpfcRQ7zxTTgZ-i425tT7hIz73Pwh9FDj4t6O3dYg,663
18
18
  twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
19
- twitter/utils/html.py,sha256=hVtIRFI2yRAdWEaShFNBG-_ZWxd16og8i8OVDnFy5Hc,1971
19
+ twitter/utils/html.py,sha256=nrOJw0vUKfBaHgFaQSQIdXfvfZ8mdu84MU_s46kJTJ4,2087
20
20
  twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
21
- tweepy_self-1.10.0b1.dist-info/METADATA,sha256=FRxYUeZHlrxUB9Jr_nbxlhEkLc1pjO-ik_XiShkwARg,9438
22
- tweepy_self-1.10.0b1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
23
- tweepy_self-1.10.0b1.dist-info/RECORD,,
21
+ tweepy_self-1.10.0b4.dist-info/METADATA,sha256=orS4YvArvrgjjz3BNRap3vua063lo2S7y8ReSGtCgdU,13152
22
+ tweepy_self-1.10.0b4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
23
+ tweepy_self-1.10.0b4.dist-info/RECORD,,
twitter/client.py CHANGED
@@ -32,7 +32,6 @@ from .base import BaseHTTPClient
32
32
  from .account import Account, AccountStatus
33
33
  from .models import User, Tweet, Media
34
34
  from .utils import (
35
- remove_at_sign,
36
35
  parse_oauth_html,
37
36
  parse_unlock_html,
38
37
  tweets_data_from_instructions,
@@ -52,7 +51,7 @@ class Client(BaseHTTPClient):
52
51
  "CreateRetweet": "ojPdsZsimiJrUGLR1sjUtA",
53
52
  "FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
54
53
  "UnfavoriteTweet": "ZYKSe-w7KEslx3JhSIk5LA",
55
- "CreateTweet": "SoVnbfCycZ7fERGCwpZkYA",
54
+ "CreateTweet": "v0en1yVV-Ybeek8ClmXwYw",
56
55
  "TweetResultByRestId": "V3vfsYzNEyD9tsf4xoFRgw",
57
56
  "ModerateTweet": "p'jF:GVqCjTcZol0xcBJjw",
58
57
  "DeleteTweet": "VaenaVgh5q5ih7kvyVjgtg",
@@ -85,7 +84,7 @@ class Client(BaseHTTPClient):
85
84
  capsolver_api_key: str = None,
86
85
  max_unlock_attempts: int = 5,
87
86
  auto_relogin: bool = True,
88
- request_self_on_startup: bool = True,
87
+ update_account_info_on_startup: bool = True,
89
88
  **session_kwargs,
90
89
  ):
91
90
  super().__init__(**session_kwargs)
@@ -94,7 +93,7 @@ class Client(BaseHTTPClient):
94
93
  self.capsolver_api_key = capsolver_api_key
95
94
  self.max_unlock_attempts = max_unlock_attempts
96
95
  self.auto_relogin = auto_relogin
97
- self.request_self_on_startup = request_self_on_startup
96
+ self._update_account_info_on_startup = update_account_info_on_startup
98
97
 
99
98
  async def __aenter__(self):
100
99
  await self.on_startup()
@@ -159,7 +158,7 @@ class Client(BaseHTTPClient):
159
158
  auth_token = self._session.cookies.get("auth_token")
160
159
  if auth_token and auth_token != self.account.auth_token:
161
160
  self.account.auth_token = auth_token
162
- logger.info(
161
+ logger.warning(
163
162
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
164
163
  f" Requested new auth_token!"
165
164
  )
@@ -239,7 +238,7 @@ class Client(BaseHTTPClient):
239
238
  reset_time = int(response.headers["x-rate-limit-reset"])
240
239
  sleep_time = reset_time - int(time()) + 1
241
240
  if sleep_time > 0:
242
- logger.info(
241
+ logger.warning(
243
242
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
244
243
  f"Rate limited! Sleep time: {sleep_time} sec."
245
244
  )
@@ -290,13 +289,13 @@ class Client(BaseHTTPClient):
290
289
 
291
290
  except Forbidden as exc:
292
291
  if 353 in exc.api_codes and "ct0" in exc.response.cookies:
293
- return await self._request(method, url, **kwargs)
292
+ return await self.request(method, url, **kwargs)
294
293
  else:
295
294
  raise
296
295
 
297
296
  async def on_startup(self):
298
- if self.request_self_on_startup:
299
- await self.request_user()
297
+ if self._update_account_info_on_startup:
298
+ await self.update_account_info()
300
299
 
301
300
  async def _request_oauth2_auth_code(
302
301
  self,
@@ -421,14 +420,13 @@ class Client(BaseHTTPClient):
421
420
 
422
421
  return authenticity_token, redirect_url
423
422
 
424
- async def request_and_set_username(self):
423
+ async def _update_account_username(self):
425
424
  url = "https://twitter.com/i/api/1.1/account/settings.json"
426
425
  response, response_json = await self.request("POST", url)
427
426
  self.account.username = response_json["screen_name"]
428
427
 
429
- async def _request_user(self, username: str) -> User:
428
+ async def _request_user_by_username(self, username: str) -> User | None:
430
429
  url, query_id = self._action_to_url("UserByScreenName")
431
- username = remove_at_sign(username)
432
430
  variables = {
433
431
  "screen_name": username,
434
432
  "withSafetyModeUserFields": True,
@@ -454,31 +452,76 @@ class Client(BaseHTTPClient):
454
452
  "fieldToggles": to_json(field_toggles),
455
453
  }
456
454
  response, data = await self.request("GET", url, params=params)
455
+ if not data["data"]:
456
+ return None
457
457
  return User.from_raw_data(data["data"]["user"]["result"])
458
458
 
459
- async def request_user(
460
- self, *, username: str = None, user_id: int | str = None
461
- ) -> User | Account:
462
- if username and user_id:
463
- raise ValueError("Specify username or user_id, not both.")
464
-
465
- if user_id:
466
- users = await self.request_users((user_id,))
467
- user = users[user_id]
468
- else:
469
- if not username:
470
- if not self.account.username:
471
- await self.request_and_set_username()
472
- username = self.account.username
459
+ async def request_user_by_username(self, username: str) -> User | Account | None:
460
+ """
461
+ :param username: Имя пользователя без знака `@`
462
+ :return: Пользователь, если существует, иначе None. Или собственный аккаунт, если совпадает имя пользователя.
463
+ """
464
+ if not self.account.username:
465
+ await self.update_account_info()
473
466
 
474
- user = await self._request_user(username)
467
+ user = await self._request_user_by_username(username)
475
468
 
476
- if self.account.username == user.username:
469
+ if user and user.id == self.account.id:
477
470
  self.account.update(**user.model_dump())
478
- user = self.account
471
+ return self.account
479
472
 
480
473
  return user
481
474
 
475
+ async def _request_users_by_ids(
476
+ self, user_ids: Iterable[str | int]
477
+ ) -> dict[int : User | Account]:
478
+ url, query_id = self._action_to_url("UsersByRestIds")
479
+ variables = {"userIds": list({str(user_id) for user_id in user_ids})}
480
+ features = {
481
+ "responsive_web_graphql_exclude_directive_enabled": True,
482
+ "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
483
+ "responsive_web_graphql_timeline_navigation_enabled": True,
484
+ "verified_phone_label_enabled": False,
485
+ }
486
+ query = {"variables": variables, "features": features}
487
+ response, data = await self.request("GET", url, params=query)
488
+
489
+ users = {}
490
+ for user_data in data["data"]["users"]:
491
+ user_data = user_data["result"]
492
+ user = User.from_raw_data(user_data)
493
+ users[user.id] = user
494
+ if user.id == self.account.id:
495
+ users[self.account.id] = self.account
496
+ return users
497
+
498
+ async def request_user_by_id(self, user_id: int | str) -> User | Account | None:
499
+ """
500
+ :param user_id: ID пользователя
501
+ :return: Пользователь, если существует, иначе None. Или собственный аккаунт, если совпадает ID.
502
+ """
503
+ if not self.account.id:
504
+ await self.update_account_info()
505
+
506
+ users = await self._request_users_by_ids((user_id,))
507
+ user = users[user_id]
508
+ return user
509
+
510
+ async def request_users_by_ids(
511
+ self, user_ids: Iterable[str | int]
512
+ ) -> dict[int : User | Account]:
513
+ """
514
+ :param user_ids: ID пользователей
515
+ :return: Пользователи, если существует, иначе None. Или собственный аккаунт, если совпадает ID.
516
+ """
517
+ return await self._request_users_by_ids(user_ids)
518
+
519
+ async def update_account_info(self):
520
+ if not self.account.username:
521
+ await self._update_account_username()
522
+
523
+ await self.request_user_by_username(self.account.username)
524
+
482
525
  async def upload_image(
483
526
  self,
484
527
  image: bytes,
@@ -494,9 +537,7 @@ class Client(BaseHTTPClient):
494
537
  :return: Media
495
538
  """
496
539
  url = "https://upload.twitter.com/1.1/media/upload.json"
497
-
498
540
  payload = {"media_data": base64.b64encode(image)}
499
-
500
541
  for attempt in range(attempts):
501
542
  try:
502
543
  response, data = await self.request(
@@ -606,6 +647,8 @@ class Client(BaseHTTPClient):
606
647
  """
607
648
  Repost (retweet)
608
649
 
650
+ Иногда может вернуть ошибку 404 (Not Found), если плохой прокси или по другим неизвестным причинам
651
+
609
652
  :return: Tweet
610
653
  """
611
654
  return await self._repost_or_search_duplicate(
@@ -613,9 +656,18 @@ class Client(BaseHTTPClient):
613
656
  )
614
657
 
615
658
  async def like(self, tweet_id: int) -> bool:
616
- response_json = await self._interact_with_tweet("FavoriteTweet", tweet_id)
617
- is_liked = response_json["data"]["favorite_tweet"] == "Done"
618
- return is_liked
659
+ """
660
+ :return: Liked or not
661
+ """
662
+ try:
663
+ response_json = await self._interact_with_tweet("FavoriteTweet", tweet_id)
664
+ except HTTPException as exc:
665
+ if 139 in exc.api_codes:
666
+ # Already liked
667
+ return True
668
+ else:
669
+ raise
670
+ return response_json["data"]["favorite_tweet"] == "Done"
619
671
 
620
672
  async def unlike(self, tweet_id: int) -> dict:
621
673
  response_json = await self._interact_with_tweet("UnfavoriteTweet", tweet_id)
@@ -662,47 +714,50 @@ class Client(BaseHTTPClient):
662
714
  attachment_url: str = None,
663
715
  ) -> Tweet:
664
716
  url, query_id = self._action_to_url("CreateTweet")
665
- payload = {
666
- "variables": {
667
- "tweet_text": text if text is not None else "",
668
- "dark_request": False,
669
- "media": {"media_entities": [], "possibly_sensitive": False},
670
- "semantic_annotation_ids": [],
671
- },
672
- "features": {
673
- "tweetypie_unmention_optimization_enabled": True,
674
- "responsive_web_edit_tweet_api_enabled": True,
675
- "graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
676
- "view_counts_everywhere_api_enabled": True,
677
- "longform_notetweets_consumption_enabled": True,
678
- "tweet_awards_web_tipping_enabled": False,
679
- "longform_notetweets_rich_text_read_enabled": True,
680
- "longform_notetweets_inline_media_enabled": True,
681
- "responsive_web_graphql_exclude_directive_enabled": True,
682
- "verified_phone_label_enabled": False,
683
- "freedom_of_speech_not_reach_fetch_enabled": True,
684
- "standardized_nudges_misinfo": True,
685
- "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": False,
686
- "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
687
- "responsive_web_graphql_timeline_navigation_enabled": True,
688
- "responsive_web_enhance_cards_enabled": False,
689
- "responsive_web_twitter_article_tweet_consumption_enabled": False,
690
- "responsive_web_media_download_video_enabled": False,
691
- },
692
- "queryId": query_id,
717
+ variables = {
718
+ "tweet_text": text if text is not None else "",
719
+ "dark_request": False,
720
+ "media": {"media_entities": [], "possibly_sensitive": False},
721
+ "semantic_annotation_ids": [],
693
722
  }
694
723
  if attachment_url:
695
- payload["variables"]["attachment_url"] = attachment_url
724
+ variables["attachment_url"] = attachment_url
696
725
  if tweet_id_to_reply:
697
- payload["variables"]["reply"] = {
726
+ variables["reply"] = {
698
727
  "in_reply_to_tweet_id": str(tweet_id_to_reply),
699
728
  "exclude_reply_user_ids": [],
700
729
  }
701
730
  if media_id:
702
- payload["variables"]["media"]["media_entities"].append(
731
+ variables["media"]["media_entities"].append(
703
732
  {"media_id": str(media_id), "tagged_users": []}
704
733
  )
705
-
734
+ features = {
735
+ "communities_web_enable_tweet_community_results_fetch": True,
736
+ "c9s_tweet_anatomy_moderator_badge_enabled": True,
737
+ "tweetypie_unmention_optimization_enabled": True,
738
+ "responsive_web_edit_tweet_api_enabled": True,
739
+ "graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
740
+ "view_counts_everywhere_api_enabled": True,
741
+ "longform_notetweets_consumption_enabled": True,
742
+ "responsive_web_twitter_article_tweet_consumption_enabled": True,
743
+ "tweet_awards_web_tipping_enabled": False,
744
+ "longform_notetweets_rich_text_read_enabled": True,
745
+ "longform_notetweets_inline_media_enabled": True,
746
+ "rweb_video_timestamps_enabled": True,
747
+ "responsive_web_graphql_exclude_directive_enabled": True,
748
+ "verified_phone_label_enabled": False,
749
+ "freedom_of_speech_not_reach_fetch_enabled": True,
750
+ "standardized_nudges_misinfo": True,
751
+ "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
752
+ "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
753
+ "responsive_web_graphql_timeline_navigation_enabled": True,
754
+ "responsive_web_enhance_cards_enabled": False,
755
+ }
756
+ payload = {
757
+ "variables": variables,
758
+ "features": features,
759
+ "queryId": query_id,
760
+ }
706
761
  response, response_json = await self.request("POST", url, json=payload)
707
762
  tweet = Tweet.from_raw_data(
708
763
  response_json["data"]["create_tweet"]["tweet_results"]["result"]
@@ -754,6 +809,11 @@ class Client(BaseHTTPClient):
754
809
  media_id: int | str = None,
755
810
  search_duplicate: bool = True,
756
811
  ) -> Tweet:
812
+ """
813
+ Иногда может вернуть ошибку 404 (Not Found), если плохой прокси или по другим неизвестным причинам
814
+
815
+ :return: Tweet
816
+ """
757
817
  return await self._tweet_or_search_duplicate(
758
818
  text,
759
819
  media_id=media_id,
@@ -768,6 +828,11 @@ class Client(BaseHTTPClient):
768
828
  media_id: int | str = None,
769
829
  search_duplicate: bool = True,
770
830
  ) -> Tweet:
831
+ """
832
+ Иногда может вернуть ошибку 404 (Not Found), если плохой прокси или по другим неизвестным причинам
833
+
834
+ :return: Tweet
835
+ """
771
836
  return await self._tweet_or_search_duplicate(
772
837
  text,
773
838
  media_id=media_id,
@@ -783,6 +848,11 @@ class Client(BaseHTTPClient):
783
848
  media_id: int | str = None,
784
849
  search_duplicate: bool = True,
785
850
  ) -> Tweet:
851
+ """
852
+ Иногда может вернуть ошибку 404 (Not Found), если плохой прокси или по другим неизвестным причинам
853
+
854
+ :return: Tweet
855
+ """
786
856
  return await self._tweet_or_search_duplicate(
787
857
  text,
788
858
  media_id=media_id,
@@ -807,8 +877,12 @@ class Client(BaseHTTPClient):
807
877
  response, response_json = await self.request("POST", url, params=params)
808
878
  return response_json
809
879
 
810
- async def _request_users(
811
- self, action: str, user_id: int | str, count: int
880
+ async def _request_users_by_action(
881
+ self,
882
+ action: str,
883
+ user_id: int | str,
884
+ count: int,
885
+ cursor: str = None,
812
886
  ) -> list[User]:
813
887
  url, query_id = self._action_to_url(action)
814
888
  variables = {
@@ -816,6 +890,8 @@ class Client(BaseHTTPClient):
816
890
  "count": count,
817
891
  "includePromotedContent": False,
818
892
  }
893
+ if cursor:
894
+ variables["cursor"] = cursor
819
895
  features = {
820
896
  "rweb_lists_timeline_redesign_enabled": True,
821
897
  "responsive_web_graphql_exclude_directive_enabled": True,
@@ -858,53 +934,46 @@ class Client(BaseHTTPClient):
858
934
  return users
859
935
 
860
936
  async def request_followers(
861
- self, user_id: int | str = None, count: int = 10
937
+ self,
938
+ user_id: int | str = None,
939
+ count: int = 20,
940
+ cursor: str = None,
862
941
  ) -> list[User]:
863
942
  """
864
943
  :param user_id: Текущий пользователь, если не передан ID иного пользователя.
865
944
  :param count: Количество подписчиков.
866
945
  """
867
946
  if user_id:
868
- return await self._request_users("Followers", user_id, count)
947
+ return await self._request_users_by_action(
948
+ "Followers", user_id, count, cursor
949
+ )
869
950
  else:
870
951
  if not self.account.id:
871
- await self.request_user()
872
- return await self._request_users("Followers", self.account.id, count)
952
+ await self.update_account_info()
953
+ return await self._request_users_by_action(
954
+ "Followers", self.account.id, count, cursor
955
+ )
873
956
 
874
957
  async def request_followings(
875
- self, user_id: int | str = None, count: int = 10
958
+ self,
959
+ user_id: int | str = None,
960
+ count: int = 20,
961
+ cursor: str = None,
876
962
  ) -> list[User]:
877
963
  """
878
964
  :param user_id: Текущий пользователь, если не передан ID иного пользователя.
879
965
  :param count: Количество подписчиков.
880
966
  """
881
967
  if user_id:
882
- return await self._request_users("Following", user_id, count)
968
+ return await self._request_users_by_action(
969
+ "Following", user_id, count, cursor
970
+ )
883
971
  else:
884
972
  if not self.account.id:
885
- await self.request_user()
886
- return await self._request_users("Following", self.account.id, count)
887
-
888
- async def request_users(
889
- self, user_ids: Iterable[str | int]
890
- ) -> dict[int : User | Account]:
891
- url, query_id = self._action_to_url("UsersByRestIds")
892
- variables = {"userIds": list({str(user_id) for user_id in user_ids})}
893
- features = {
894
- "responsive_web_graphql_exclude_directive_enabled": True,
895
- "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
896
- "responsive_web_graphql_timeline_navigation_enabled": True,
897
- "verified_phone_label_enabled": False,
898
- }
899
- query = {"variables": variables, "features": features}
900
- response, data = await self.request("GET", url, params=query)
901
-
902
- users = {}
903
- for user_data in data["data"]["users"]:
904
- user_data = user_data["result"]
905
- user = User.from_raw_data(user_data)
906
- users[user.id] = user
907
- return users
973
+ await self.update_account_info()
974
+ return await self._request_users_by_action(
975
+ "Following", self.account.id, count, cursor
976
+ )
908
977
 
909
978
  async def _request_tweet(self, tweet_id: int | str) -> Tweet:
910
979
  url, query_id = self._action_to_url("TweetDetail")
@@ -947,7 +1016,9 @@ class Client(BaseHTTPClient):
947
1016
  tweet_data = tweets_data_from_instructions(instructions)[0]
948
1017
  return Tweet.from_raw_data(tweet_data)
949
1018
 
950
- async def _request_tweets(self, user_id: int | str, count: int = 20) -> list[Tweet]:
1019
+ async def _request_tweets(
1020
+ self, user_id: int | str, count: int = 20, cursor: str = None
1021
+ ) -> list[Tweet]:
951
1022
  url, query_id = self._action_to_url("UserTweets")
952
1023
  variables = {
953
1024
  "userId": str(user_id),
@@ -957,6 +1028,8 @@ class Client(BaseHTTPClient):
957
1028
  "withVoice": True,
958
1029
  "withV2Timeline": True,
959
1030
  }
1031
+ if cursor:
1032
+ variables["cursor"] = cursor
960
1033
  features = {
961
1034
  "responsive_web_graphql_exclude_directive_enabled": True,
962
1035
  "verified_phone_label_enabled": False,
@@ -993,16 +1066,14 @@ class Client(BaseHTTPClient):
993
1066
  return await self._request_tweet(tweet_id)
994
1067
 
995
1068
  async def request_tweets(
996
- self,
997
- user_id: int | str = None,
998
- count: int = 20,
1069
+ self, user_id: int | str = None, count: int = 20, cursor: str = None
999
1070
  ) -> list[Tweet]:
1000
1071
  if not user_id:
1001
1072
  if not self.account.id:
1002
- await self.request_user()
1073
+ await self.update_account_info()
1003
1074
  user_id = self.account.id
1004
1075
 
1005
- return await self._request_tweets(user_id, count)
1076
+ return await self._request_tweets(user_id, count, cursor)
1006
1077
 
1007
1078
  async def _update_profile_image(
1008
1079
  self, type: Literal["banner", "image"], media_id: str | int
@@ -1068,9 +1139,6 @@ class Client(BaseHTTPClient):
1068
1139
  }
1069
1140
  response, data = await self.request("POST", url, data=payload)
1070
1141
  changed = data["status"] == "ok"
1071
- # TODO Делать это автоматически в методе request
1072
- auth_token = response.cookies.get("auth_token", domain=".twitter.com")
1073
- self.account.auth_token = auth_token
1074
1142
  self.account.password = password
1075
1143
  return changed
1076
1144
 
@@ -1088,7 +1156,6 @@ class Client(BaseHTTPClient):
1088
1156
  raise ValueError("Specify at least one param")
1089
1157
 
1090
1158
  url = "https://twitter.com/i/api/1.1/account/update_profile.json"
1091
- # headers = {"content-type": "application/x-www-form-urlencoded"}
1092
1159
  # Создаем словарь data, включая в него только те ключи, для которых значения не равны None
1093
1160
  payload = {
1094
1161
  k: v
@@ -1100,7 +1167,6 @@ class Client(BaseHTTPClient):
1100
1167
  ]
1101
1168
  if v is not None
1102
1169
  }
1103
- # response, response_json = await self.request("POST", url, headers=headers, data=payload)
1104
1170
  response, data = await self.request("POST", url, data=payload)
1105
1171
  # Проверяем, что все переданные параметры соответствуют полученным
1106
1172
  updated = all(
@@ -1110,7 +1176,7 @@ class Client(BaseHTTPClient):
1110
1176
  updated &= URL(website) == URL(
1111
1177
  data["entities"]["url"]["urls"][0]["expanded_url"]
1112
1178
  )
1113
- await self.request_user()
1179
+ await self.update_account_info()
1114
1180
  return updated
1115
1181
 
1116
1182
  async def establish_status(self):
@@ -1129,17 +1195,14 @@ class Client(BaseHTTPClient):
1129
1195
  year_visibility: Literal["self"] = "self",
1130
1196
  ) -> bool:
1131
1197
  url = "https://twitter.com/i/api/1.1/account/update_profile.json"
1132
- headers = {"content-type": "application/x-www-form-urlencoded"}
1133
- data = {
1198
+ payload = {
1134
1199
  "birthdate_day": day,
1135
1200
  "birthdate_month": month,
1136
1201
  "birthdate_year": year,
1137
1202
  "birthdate_visibility": visibility,
1138
1203
  "birthdate_year_visibility": year_visibility,
1139
1204
  }
1140
- response, response_json = await self.request(
1141
- "POST", url, headers=headers, data=data
1142
- )
1205
+ response, response_json = await self.request("POST", url, json=payload)
1143
1206
  birthdate_data = response_json["extended_profile"]["birthdate"]
1144
1207
  updated = all(
1145
1208
  (
@@ -1249,6 +1312,8 @@ class Client(BaseHTTPClient):
1249
1312
  payload["verification_string"] = verification_string
1250
1313
  payload["language_code"] = "en"
1251
1314
 
1315
+ # TODO ui_metrics
1316
+
1252
1317
  return await self.request("POST", self._CAPTCHA_URL, data=payload, bearer=False)
1253
1318
 
1254
1319
  async def unlock(self):
@@ -1262,9 +1327,23 @@ class Client(BaseHTTPClient):
1262
1327
  needs_unlock,
1263
1328
  start_button,
1264
1329
  finish_button,
1330
+ delete_button,
1265
1331
  ) = parse_unlock_html(html)
1266
1332
  attempt = 1
1267
1333
 
1334
+ if delete_button:
1335
+ response, html = await self._confirm_unlock(
1336
+ authenticity_token, assignment_token
1337
+ )
1338
+ (
1339
+ authenticity_token,
1340
+ assignment_token,
1341
+ needs_unlock,
1342
+ start_button,
1343
+ finish_button,
1344
+ delete_button,
1345
+ ) = parse_unlock_html(html)
1346
+
1268
1347
  if start_button or finish_button:
1269
1348
  response, html = await self._confirm_unlock(
1270
1349
  authenticity_token, assignment_token
@@ -1275,6 +1354,7 @@ class Client(BaseHTTPClient):
1275
1354
  needs_unlock,
1276
1355
  start_button,
1277
1356
  finish_button,
1357
+ delete_button,
1278
1358
  ) = parse_unlock_html(html)
1279
1359
 
1280
1360
  funcaptcha = {
@@ -1321,6 +1401,7 @@ class Client(BaseHTTPClient):
1321
1401
  needs_unlock,
1322
1402
  start_button,
1323
1403
  finish_button,
1404
+ delete_button,
1324
1405
  ) = parse_unlock_html(html)
1325
1406
 
1326
1407
  if finish_button:
@@ -1333,6 +1414,7 @@ class Client(BaseHTTPClient):
1333
1414
  needs_unlock,
1334
1415
  start_button,
1335
1416
  finish_button,
1417
+ delete_button,
1336
1418
  ) = parse_unlock_html(html)
1337
1419
 
1338
1420
  attempt += 1
@@ -1565,7 +1647,7 @@ class Client(BaseHTTPClient):
1565
1647
 
1566
1648
  async def totp_is_enabled(self):
1567
1649
  if not self.account.id:
1568
- await self.request_user()
1650
+ await self.update_account_info()
1569
1651
 
1570
1652
  url = f"https://twitter.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
1571
1653
  response, data = await self.request("GET", url)
twitter/utils/html.py CHANGED
@@ -21,9 +21,11 @@ def parse_oauth_html(html: str) -> tuple[str | None, str | None, str | None]:
21
21
  return authenticity_token, redirect_url, redirect_after_login_url
22
22
 
23
23
 
24
- def parse_unlock_html(html: str) -> tuple[str | None, str | None, bool, bool, bool]:
24
+ def parse_unlock_html(
25
+ html: str,
26
+ ) -> tuple[str | None, str | None, bool, bool, bool, bool]:
25
27
  """
26
- :return: authenticity_token, assignment_token, needs_unlock, start_button, finish_button
28
+ :return: authenticity_token, assignment_token, needs_unlock, start_button, finish_button, delete_button
27
29
  """
28
30
  soup = BeautifulSoup(html, "lxml")
29
31
  authenticity_token_element = soup.find("input", {"name": "authenticity_token"})
@@ -38,10 +40,12 @@ def parse_unlock_html(html: str) -> tuple[str | None, str | None, bool, bool, bo
38
40
  needs_unlock = bool(verification_string)
39
41
  start_button = bool(soup.find("input", value="Start"))
40
42
  finish_button = bool(soup.find("input", value="Continue to X"))
43
+ delete_button = bool(soup.find("input", value="Delete"))
41
44
  return (
42
45
  authenticity_token,
43
46
  assignment_token,
44
47
  needs_unlock,
45
48
  start_button,
46
49
  finish_button,
50
+ delete_button,
47
51
  )
@@ -1,225 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: tweepy-self
3
- Version: 1.10.0b1
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 (>=1)
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
- - Docs (soon)
36
-
37
- More libraries of the family:
38
- - [better-proxy](https://github.com/alenkimov/better_proxy)
39
- - [better-web3](https://github.com/alenkimov/better_web3)
40
-
41
- Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
42
-
43
- ## Key Features
44
- - Modern Pythonic API using async and await.
45
- - Prevents user account automation detection.
46
-
47
- ## Installing
48
- ```bash
49
- pip install tweepy-self
50
- ```
51
-
52
- ## Example
53
- ```python
54
- import asyncio
55
- import twitter
56
-
57
- account = twitter.Account(auth_token="auth_token")
58
-
59
- async def main():
60
- async with twitter.Client(account) as twitter_client:
61
- await twitter_client.tweet("Hello, tweepy-self! <3")
62
-
63
- asyncio.run(main())
64
- ```
65
-
66
- ## More
67
- 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
68
-
69
- ## Документация (устаревшая)
70
- `Код ушел немного дальше, чем эта документация.`
71
-
72
- Библиотека позволяет работать с неофициальным API Twitter, а именно:
73
- - Логин
74
- - Анлок
75
- - Привязывать сервисы (приложения).
76
- - Устанавливать статус аккаунта (бан, лок).
77
- - Загружать изображения на сервер и изменять баннер и аватарку.
78
- - Изменять данные о пользователе: имя, описание профиля и другое.
79
- - Изменять имя пользователя и пароль.
80
- - Запрашивать информацию о подписчиках.
81
- - Запрашивать некоторую информацию о пользователе (количество подписчиков и другое).
82
- - Голосовать.
83
- - Подписываться и отписываться.
84
- - Лайкать и дизлайкать.
85
- - Твиттить, ретвиттить с изображением и без.
86
- - Закреплять твиты.
87
- - Запрашивать твиты пользователей.
88
- - Удалять твиты.
89
- - И другое.
90
-
91
- #### Статус аккаунта
92
- После любого взаимодействия с Twitter устанавливается статус аккаунта:
93
- - `BAD_TOKEN` - Неверный токен.
94
- - `UNKNOWN` - Статус аккаунта не установлен.
95
- - `SUSPENDED` - Действие учетной записи приостановлено (бан).
96
- - `LOCKED` - Учетная запись заморожена (лок) (требуется прохождение капчи).
97
- - `GOOD` - Аккаунт в порядке.
98
-
99
- Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
100
- Например, простой запрос данных об аккаунте честно вернет данные, даже если ваш аккаунт заморожен.
101
-
102
- Для достоверной установки статуса аккаунта используйте метод `establish_status()`
103
-
104
- ### Примеры работы
105
- Запрос информации о пользователе:
106
-
107
- ```python
108
- # Запрос информации о текущем пользователе:
109
- me = await twitter_client.request_user()
110
- print(f"[{account.short_auth_token}] {me}")
111
- print(f"Аккаунт создан: {me.created_at}")
112
- print(f"Following (подписан ты): {me.followings_count}")
113
- print(f"Followers (подписаны на тебя): {me.followers_count}")
114
- print(f"Прочая информация: {me.raw_data}")
115
-
116
- # Запрос информации об ином пользователе:
117
- elonmusk = await twitter.request_user("@elonmusk")
118
- print(elonmusk)
119
- ```
120
-
121
- Смена имени пользователя и пароля:
122
-
123
- ```python
124
- account = twitter.Account("auth_token", password="password")
125
- ...
126
- await twitter_client.change_username("new_username")
127
- await twitter_client.request_user()
128
- print(f"New username: {account.username}")
129
-
130
- await twitter_client.change_password("new_password")
131
- print(f"New password: {account.password}")
132
- print(f"New auth_token: {account.auth_token}")
133
- ```
134
-
135
- Смена данных профиля:
136
- ```python
137
- await twitter_client.update_birthdate(day=1, month=12, year=2000)
138
- await twitter_client.update_profile( # Locks account!
139
- name="New Name",
140
- description="New description",
141
- location="New York",
142
- website="https://github.com/alenkimov/better_automation",
143
- )
144
- ```
145
-
146
- Загрузка изображений и смена аватара и баннера:
147
- ```python
148
- image = open(f"image.png", "rb").read()
149
- media_id = await twitter_client.upload_image(image)
150
- avatar_image_url = await twitter_client.update_profile_avatar(media_id)
151
- banner_image_url = await twitter_client.update_profile_banner(media_id)
152
- ```
153
-
154
- Привязка сервиса (приложения):
155
-
156
- ```python
157
- # Изучите запросы сервиса и найдите подобные данные для авторизации (привязки):
158
- bind_data = {
159
- 'response_type': 'code',
160
- 'client_id': 'TjFVQm52ZDFGWEtNT0tKaktaSWU6MTpjaQ',
161
- 'redirect_uri': 'https://waitlist.lens.xyz/tw/',
162
- 'scope': 'users.read tweet.read offline.access',
163
- 'state': 'state', # Может быть как статичным, так и динамическим.
164
- 'code_challenge': 'challenge',
165
- 'code_challenge_method': 'plain'
166
- }
167
-
168
- bind_code = await twitter_client.oauth_2(**bind_data)
169
- # Передайте код авторизации (привязки) сервису.
170
- # Сервис также может потребовать state, если он динамический.
171
- ```
172
-
173
- Отправка сообщения:
174
-
175
- ```python
176
- bro = await twitter_client.request_user("@username")
177
- await twitter_client.send_message(bro.id, "I love you!")
178
- ```
179
-
180
- Запрос входящих сообщений:
181
- ```python
182
- messages = await twitter_client.request_messages()
183
- for message in messages:
184
- message_data = message["message_data"]
185
- recipient_id = message_data["recipient_id"]
186
- sender_id = message_data["sender_id"]
187
- text = message_data["text"]
188
- print(f"[id {sender_id}] -> [id {recipient_id}]: {text}")
189
- ```
190
-
191
- Другие методы:
192
- ```python
193
- # Выражение любви через твит
194
- tweet_id = await twitter_client.tweet("I love YOU! !!!!1!1")
195
- print(f"Любовь выражена! Tweet id: {tweet_id}")
196
-
197
- print(f"Tweet is pined: {await twitter_client.pin_tweet(tweet_id)}")
198
-
199
- # Лайк
200
- print(f"Tweet {tweet_id} is liked: {await twitter_client.like(tweet_id)}")
201
-
202
- # Репост (ретвит)
203
- print(f"Tweet {tweet_id} is retweeted. Tweet id: {await twitter_client.repost(tweet_id)}")
204
-
205
- # Коммент (реплай)
206
- print(f"Tweet {tweet_id} is replied. Reply id: {await twitter_client.reply(tweet_id, 'tem razão')}")
207
-
208
- # Подписываемся на Илона Маска
209
- print(f"@{elonmusk.username} is followed: {await twitter_client.follow(elonmusk.id)}")
210
-
211
- # Отписываемся от Илона Маска
212
- print(f"@{elonmusk.username} is unfollowed: {await twitter_client.unfollow(elonmusk.id)}")
213
-
214
- tweet_url = 'https://twitter.com/CreamIce_Cone/status/1691735090529976489'
215
- # Цитата (Quote tweet)
216
- quote_tweet_id = await twitter_client.quote(tweet_url, 'oh....')
217
- print(f"Quoted! Tweet id: {quote_tweet_id}")
218
-
219
- # Запрашиваем первых трех подписчиков
220
- # (Параметр count по каким-то причинам работает некорректно)
221
- followers = await twitter_client.request_followers(count=20)
222
- print("Твои подписчики:")
223
- for follower in followers:
224
- print(follower)
225
- ```