tweepy-self 1.10.0b3__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.
- tweepy_self-1.10.0b4.dist-info/METADATA +303 -0
- {tweepy_self-1.10.0b3.dist-info → tweepy_self-1.10.0b4.dist-info}/RECORD +5 -5
- twitter/client.py +124 -84
- twitter/utils/html.py +6 -2
- tweepy_self-1.10.0b3.dist-info/METADATA +0 -225
- {tweepy_self-1.10.0b3.dist-info → tweepy_self-1.10.0b4.dist-info}/WHEEL +0 -0
@@ -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
|
+
[](https://t.me/cum_insider)
|
29
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
30
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
31
|
+
[](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=
|
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=
|
19
|
+
twitter/utils/html.py,sha256=nrOJw0vUKfBaHgFaQSQIdXfvfZ8mdu84MU_s46kJTJ4,2087
|
20
20
|
twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
|
21
|
-
tweepy_self-1.10.
|
22
|
-
tweepy_self-1.10.
|
23
|
-
tweepy_self-1.10.
|
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,
|
@@ -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
|
-
|
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.
|
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()
|
@@ -295,8 +294,8 @@ class Client(BaseHTTPClient):
|
|
295
294
|
raise
|
296
295
|
|
297
296
|
async def on_startup(self):
|
298
|
-
if self.
|
299
|
-
await self.
|
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
|
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
|
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,
|
@@ -458,40 +456,72 @@ class Client(BaseHTTPClient):
|
|
458
456
|
return None
|
459
457
|
return User.from_raw_data(data["data"]["user"]["result"])
|
460
458
|
|
461
|
-
async def
|
462
|
-
self, *, username: str = None, user_id: int | str = None
|
463
|
-
) -> User | Account | None:
|
459
|
+
async def request_user_by_username(self, username: str) -> User | Account | None:
|
464
460
|
"""
|
465
|
-
|
461
|
+
:param username: Имя пользователя без знака `@`
|
462
|
+
:return: Пользователь, если существует, иначе None. Или собственный аккаунт, если совпадает имя пользователя.
|
466
463
|
"""
|
467
|
-
if username
|
468
|
-
|
464
|
+
if not self.account.username:
|
465
|
+
await self.update_account_info()
|
469
466
|
|
470
|
-
|
471
|
-
users = await self.request_users((user_id,))
|
472
|
-
user = users[user_id]
|
473
|
-
elif username:
|
474
|
-
user = await self._request_user(username)
|
475
|
-
else:
|
476
|
-
if not self.account.username:
|
477
|
-
await self.request_and_set_username()
|
467
|
+
user = await self._request_user_by_username(username)
|
478
468
|
|
479
|
-
|
469
|
+
if user and user.id == self.account.id:
|
470
|
+
self.account.update(**user.model_dump())
|
471
|
+
return self.account
|
480
472
|
|
481
|
-
|
482
|
-
bad_username = self.account.username
|
483
|
-
await self.request_and_set_username()
|
484
|
-
user = await self._request_user(self.account.username)
|
485
|
-
logger.warning(
|
486
|
-
f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
|
487
|
-
f" Bad username: {bad_username}. Requested a real username."
|
488
|
-
)
|
473
|
+
return user
|
489
474
|
|
490
|
-
|
491
|
-
|
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)
|
492
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]
|
493
508
|
return user
|
494
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
|
+
|
495
525
|
async def upload_image(
|
496
526
|
self,
|
497
527
|
image: bytes,
|
@@ -507,9 +537,7 @@ class Client(BaseHTTPClient):
|
|
507
537
|
:return: Media
|
508
538
|
"""
|
509
539
|
url = "https://upload.twitter.com/1.1/media/upload.json"
|
510
|
-
|
511
540
|
payload = {"media_data": base64.b64encode(image)}
|
512
|
-
|
513
541
|
for attempt in range(attempts):
|
514
542
|
try:
|
515
543
|
response, data = await self.request(
|
@@ -849,8 +877,12 @@ class Client(BaseHTTPClient):
|
|
849
877
|
response, response_json = await self.request("POST", url, params=params)
|
850
878
|
return response_json
|
851
879
|
|
852
|
-
async def
|
853
|
-
self,
|
880
|
+
async def _request_users_by_action(
|
881
|
+
self,
|
882
|
+
action: str,
|
883
|
+
user_id: int | str,
|
884
|
+
count: int,
|
885
|
+
cursor: str = None,
|
854
886
|
) -> list[User]:
|
855
887
|
url, query_id = self._action_to_url(action)
|
856
888
|
variables = {
|
@@ -858,6 +890,8 @@ class Client(BaseHTTPClient):
|
|
858
890
|
"count": count,
|
859
891
|
"includePromotedContent": False,
|
860
892
|
}
|
893
|
+
if cursor:
|
894
|
+
variables["cursor"] = cursor
|
861
895
|
features = {
|
862
896
|
"rweb_lists_timeline_redesign_enabled": True,
|
863
897
|
"responsive_web_graphql_exclude_directive_enabled": True,
|
@@ -900,53 +934,46 @@ class Client(BaseHTTPClient):
|
|
900
934
|
return users
|
901
935
|
|
902
936
|
async def request_followers(
|
903
|
-
self,
|
937
|
+
self,
|
938
|
+
user_id: int | str = None,
|
939
|
+
count: int = 20,
|
940
|
+
cursor: str = None,
|
904
941
|
) -> list[User]:
|
905
942
|
"""
|
906
943
|
:param user_id: Текущий пользователь, если не передан ID иного пользователя.
|
907
944
|
:param count: Количество подписчиков.
|
908
945
|
"""
|
909
946
|
if user_id:
|
910
|
-
return await self.
|
947
|
+
return await self._request_users_by_action(
|
948
|
+
"Followers", user_id, count, cursor
|
949
|
+
)
|
911
950
|
else:
|
912
951
|
if not self.account.id:
|
913
|
-
await self.
|
914
|
-
return await self.
|
952
|
+
await self.update_account_info()
|
953
|
+
return await self._request_users_by_action(
|
954
|
+
"Followers", self.account.id, count, cursor
|
955
|
+
)
|
915
956
|
|
916
957
|
async def request_followings(
|
917
|
-
self,
|
958
|
+
self,
|
959
|
+
user_id: int | str = None,
|
960
|
+
count: int = 20,
|
961
|
+
cursor: str = None,
|
918
962
|
) -> list[User]:
|
919
963
|
"""
|
920
964
|
:param user_id: Текущий пользователь, если не передан ID иного пользователя.
|
921
965
|
:param count: Количество подписчиков.
|
922
966
|
"""
|
923
967
|
if user_id:
|
924
|
-
return await self.
|
968
|
+
return await self._request_users_by_action(
|
969
|
+
"Following", user_id, count, cursor
|
970
|
+
)
|
925
971
|
else:
|
926
972
|
if not self.account.id:
|
927
|
-
await self.
|
928
|
-
return await self.
|
929
|
-
|
930
|
-
|
931
|
-
self, user_ids: Iterable[str | int]
|
932
|
-
) -> dict[int : User | Account]:
|
933
|
-
url, query_id = self._action_to_url("UsersByRestIds")
|
934
|
-
variables = {"userIds": list({str(user_id) for user_id in user_ids})}
|
935
|
-
features = {
|
936
|
-
"responsive_web_graphql_exclude_directive_enabled": True,
|
937
|
-
"responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
|
938
|
-
"responsive_web_graphql_timeline_navigation_enabled": True,
|
939
|
-
"verified_phone_label_enabled": False,
|
940
|
-
}
|
941
|
-
query = {"variables": variables, "features": features}
|
942
|
-
response, data = await self.request("GET", url, params=query)
|
943
|
-
|
944
|
-
users = {}
|
945
|
-
for user_data in data["data"]["users"]:
|
946
|
-
user_data = user_data["result"]
|
947
|
-
user = User.from_raw_data(user_data)
|
948
|
-
users[user.id] = user
|
949
|
-
return users
|
973
|
+
await self.update_account_info()
|
974
|
+
return await self._request_users_by_action(
|
975
|
+
"Following", self.account.id, count, cursor
|
976
|
+
)
|
950
977
|
|
951
978
|
async def _request_tweet(self, tweet_id: int | str) -> Tweet:
|
952
979
|
url, query_id = self._action_to_url("TweetDetail")
|
@@ -989,7 +1016,9 @@ class Client(BaseHTTPClient):
|
|
989
1016
|
tweet_data = tweets_data_from_instructions(instructions)[0]
|
990
1017
|
return Tweet.from_raw_data(tweet_data)
|
991
1018
|
|
992
|
-
async def _request_tweets(
|
1019
|
+
async def _request_tweets(
|
1020
|
+
self, user_id: int | str, count: int = 20, cursor: str = None
|
1021
|
+
) -> list[Tweet]:
|
993
1022
|
url, query_id = self._action_to_url("UserTweets")
|
994
1023
|
variables = {
|
995
1024
|
"userId": str(user_id),
|
@@ -999,6 +1028,8 @@ class Client(BaseHTTPClient):
|
|
999
1028
|
"withVoice": True,
|
1000
1029
|
"withV2Timeline": True,
|
1001
1030
|
}
|
1031
|
+
if cursor:
|
1032
|
+
variables["cursor"] = cursor
|
1002
1033
|
features = {
|
1003
1034
|
"responsive_web_graphql_exclude_directive_enabled": True,
|
1004
1035
|
"verified_phone_label_enabled": False,
|
@@ -1035,16 +1066,14 @@ class Client(BaseHTTPClient):
|
|
1035
1066
|
return await self._request_tweet(tweet_id)
|
1036
1067
|
|
1037
1068
|
async def request_tweets(
|
1038
|
-
self,
|
1039
|
-
user_id: int | str = None,
|
1040
|
-
count: int = 20,
|
1069
|
+
self, user_id: int | str = None, count: int = 20, cursor: str = None
|
1041
1070
|
) -> list[Tweet]:
|
1042
1071
|
if not user_id:
|
1043
1072
|
if not self.account.id:
|
1044
|
-
await self.
|
1073
|
+
await self.update_account_info()
|
1045
1074
|
user_id = self.account.id
|
1046
1075
|
|
1047
|
-
return await self._request_tweets(user_id, count)
|
1076
|
+
return await self._request_tweets(user_id, count, cursor)
|
1048
1077
|
|
1049
1078
|
async def _update_profile_image(
|
1050
1079
|
self, type: Literal["banner", "image"], media_id: str | int
|
@@ -1110,9 +1139,6 @@ class Client(BaseHTTPClient):
|
|
1110
1139
|
}
|
1111
1140
|
response, data = await self.request("POST", url, data=payload)
|
1112
1141
|
changed = data["status"] == "ok"
|
1113
|
-
# TODO Делать это автоматически в методе request
|
1114
|
-
auth_token = response.cookies.get("auth_token", domain=".twitter.com")
|
1115
|
-
self.account.auth_token = auth_token
|
1116
1142
|
self.account.password = password
|
1117
1143
|
return changed
|
1118
1144
|
|
@@ -1130,7 +1156,6 @@ class Client(BaseHTTPClient):
|
|
1130
1156
|
raise ValueError("Specify at least one param")
|
1131
1157
|
|
1132
1158
|
url = "https://twitter.com/i/api/1.1/account/update_profile.json"
|
1133
|
-
# headers = {"content-type": "application/x-www-form-urlencoded"}
|
1134
1159
|
# Создаем словарь data, включая в него только те ключи, для которых значения не равны None
|
1135
1160
|
payload = {
|
1136
1161
|
k: v
|
@@ -1142,7 +1167,6 @@ class Client(BaseHTTPClient):
|
|
1142
1167
|
]
|
1143
1168
|
if v is not None
|
1144
1169
|
}
|
1145
|
-
# response, response_json = await self.request("POST", url, headers=headers, data=payload)
|
1146
1170
|
response, data = await self.request("POST", url, data=payload)
|
1147
1171
|
# Проверяем, что все переданные параметры соответствуют полученным
|
1148
1172
|
updated = all(
|
@@ -1152,7 +1176,7 @@ class Client(BaseHTTPClient):
|
|
1152
1176
|
updated &= URL(website) == URL(
|
1153
1177
|
data["entities"]["url"]["urls"][0]["expanded_url"]
|
1154
1178
|
)
|
1155
|
-
await self.
|
1179
|
+
await self.update_account_info()
|
1156
1180
|
return updated
|
1157
1181
|
|
1158
1182
|
async def establish_status(self):
|
@@ -1171,17 +1195,14 @@ class Client(BaseHTTPClient):
|
|
1171
1195
|
year_visibility: Literal["self"] = "self",
|
1172
1196
|
) -> bool:
|
1173
1197
|
url = "https://twitter.com/i/api/1.1/account/update_profile.json"
|
1174
|
-
|
1175
|
-
data = {
|
1198
|
+
payload = {
|
1176
1199
|
"birthdate_day": day,
|
1177
1200
|
"birthdate_month": month,
|
1178
1201
|
"birthdate_year": year,
|
1179
1202
|
"birthdate_visibility": visibility,
|
1180
1203
|
"birthdate_year_visibility": year_visibility,
|
1181
1204
|
}
|
1182
|
-
response, response_json = await self.request(
|
1183
|
-
"POST", url, headers=headers, data=data
|
1184
|
-
)
|
1205
|
+
response, response_json = await self.request("POST", url, json=payload)
|
1185
1206
|
birthdate_data = response_json["extended_profile"]["birthdate"]
|
1186
1207
|
updated = all(
|
1187
1208
|
(
|
@@ -1291,6 +1312,8 @@ class Client(BaseHTTPClient):
|
|
1291
1312
|
payload["verification_string"] = verification_string
|
1292
1313
|
payload["language_code"] = "en"
|
1293
1314
|
|
1315
|
+
# TODO ui_metrics
|
1316
|
+
|
1294
1317
|
return await self.request("POST", self._CAPTCHA_URL, data=payload, bearer=False)
|
1295
1318
|
|
1296
1319
|
async def unlock(self):
|
@@ -1304,9 +1327,23 @@ class Client(BaseHTTPClient):
|
|
1304
1327
|
needs_unlock,
|
1305
1328
|
start_button,
|
1306
1329
|
finish_button,
|
1330
|
+
delete_button,
|
1307
1331
|
) = parse_unlock_html(html)
|
1308
1332
|
attempt = 1
|
1309
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
|
+
|
1310
1347
|
if start_button or finish_button:
|
1311
1348
|
response, html = await self._confirm_unlock(
|
1312
1349
|
authenticity_token, assignment_token
|
@@ -1317,6 +1354,7 @@ class Client(BaseHTTPClient):
|
|
1317
1354
|
needs_unlock,
|
1318
1355
|
start_button,
|
1319
1356
|
finish_button,
|
1357
|
+
delete_button,
|
1320
1358
|
) = parse_unlock_html(html)
|
1321
1359
|
|
1322
1360
|
funcaptcha = {
|
@@ -1363,6 +1401,7 @@ class Client(BaseHTTPClient):
|
|
1363
1401
|
needs_unlock,
|
1364
1402
|
start_button,
|
1365
1403
|
finish_button,
|
1404
|
+
delete_button,
|
1366
1405
|
) = parse_unlock_html(html)
|
1367
1406
|
|
1368
1407
|
if finish_button:
|
@@ -1375,6 +1414,7 @@ class Client(BaseHTTPClient):
|
|
1375
1414
|
needs_unlock,
|
1376
1415
|
start_button,
|
1377
1416
|
finish_button,
|
1417
|
+
delete_button,
|
1378
1418
|
) = parse_unlock_html(html)
|
1379
1419
|
|
1380
1420
|
attempt += 1
|
@@ -1607,7 +1647,7 @@ class Client(BaseHTTPClient):
|
|
1607
1647
|
|
1608
1648
|
async def totp_is_enabled(self):
|
1609
1649
|
if not self.account.id:
|
1610
|
-
await self.
|
1650
|
+
await self.update_account_info()
|
1611
1651
|
|
1612
1652
|
url = f"https://twitter.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
|
1613
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(
|
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.0b3
|
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)
|
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
|
-
[](https://t.me/cum_insider)
|
29
|
-
[](https://pypi.python.org/pypi/tweepy-self)
|
30
|
-
[](https://pypi.python.org/pypi/tweepy-self)
|
31
|
-
[](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
|
-
```
|
File without changes
|