tweepy-self 0.1.0__tar.gz → 1.0.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- tweepy_self-1.0.0/PKG-INFO +216 -0
- tweepy_self-1.0.0/README.md +196 -0
- {tweepy_self-0.1.0 → tweepy_self-1.0.0}/pyproject.toml +10 -3
- tweepy_self-1.0.0/twitter/__init__.py +26 -0
- tweepy_self-1.0.0/twitter/account.py +98 -0
- tweepy_self-1.0.0/twitter/base/__init__.py +7 -0
- tweepy_self-1.0.0/twitter/base/client.py +20 -0
- tweepy_self-1.0.0/twitter/base/session.py +56 -0
- tweepy_self-1.0.0/twitter/client.py +1220 -0
- tweepy_self-1.0.0/twitter/errors.py +145 -0
- tweepy_self-1.0.0/twitter/models.py +64 -0
- tweepy_self-1.0.0/twitter/utils/__init__.py +36 -0
- tweepy_self-1.0.0/twitter/utils/file.py +41 -0
- tweepy_self-1.0.0/twitter/utils/html.py +29 -0
- tweepy_self-1.0.0/twitter/utils/other.py +24 -0
- tweepy_self-0.1.0/PKG-INFO +0 -15
- tweepy_self-0.1.0/README.md +0 -3
- tweepy_self-0.1.0/tweepy-self/__init__.py +0 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: tweepy-self
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Twitter (selfbot) for Python!
|
5
|
+
Author: Alen
|
6
|
+
Author-email: alen.kimov@gmail.com
|
7
|
+
Requires-Python: >=3.11,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
11
|
+
Requires-Dist: beautifulsoup4 (>=4,<5)
|
12
|
+
Requires-Dist: better-proxy (==0.5.0)
|
13
|
+
Requires-Dist: curl_cffi (==0.6.0b9)
|
14
|
+
Requires-Dist: lxml (>=5,<6)
|
15
|
+
Requires-Dist: pydantic (>=2,<3)
|
16
|
+
Requires-Dist: pyotp (>=2,<3)
|
17
|
+
Requires-Dist: python3-capsolver (>=0.9,<0.10)
|
18
|
+
Requires-Dist: yarl (>=1,<2)
|
19
|
+
Description-Content-Type: text/markdown
|
20
|
+
|
21
|
+
# Tweepy-self
|
22
|
+
[](https://t.me/cum_insider)
|
23
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
24
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
25
|
+
|
26
|
+
A modern, easy to use, feature-rich, and async ready API wrapper for Twitter's user API written in Python.
|
27
|
+
|
28
|
+
- Docs (soon)
|
29
|
+
|
30
|
+
More libraries of the family:
|
31
|
+
- [better-web3](https://github.com/alenkimov/better_web3)
|
32
|
+
- [better-proxy](https://github.com/alenkimov/better_proxy)
|
33
|
+
- [better-automation](https://github.com/alenkimov/better_automation)
|
34
|
+
|
35
|
+
Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
|
36
|
+
|
37
|
+
## Key Features
|
38
|
+
- Modern Pythonic API using async and await.
|
39
|
+
- Prevents user account automation detection.
|
40
|
+
|
41
|
+
## Installing
|
42
|
+
```bash
|
43
|
+
pip install tweepy-self
|
44
|
+
```
|
45
|
+
|
46
|
+
## Example
|
47
|
+
```python
|
48
|
+
import asyncio
|
49
|
+
import twitter
|
50
|
+
|
51
|
+
account = twitter.Account(auth_token="auth_token")
|
52
|
+
|
53
|
+
async def main():
|
54
|
+
async with twitter.Client(account) as twitter_client:
|
55
|
+
await twitter_client.tweet("Hello, tweepy-self! <3")
|
56
|
+
|
57
|
+
asyncio.run(main())
|
58
|
+
```
|
59
|
+
|
60
|
+
## More
|
61
|
+
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
|
62
|
+
|
63
|
+
## Документация (устаревшая)
|
64
|
+
`Код ушел немного дальше, чем эта документация.`
|
65
|
+
|
66
|
+
Библиотека позволяет работать с неофициальным API Twitter, а именно:
|
67
|
+
- Логин
|
68
|
+
- Анлок
|
69
|
+
- Привязывать сервисы (приложения).
|
70
|
+
- Устанавливать статус аккаунта (бан, лок).
|
71
|
+
- Загружать изображения на сервер и изменять баннер и аватарку.
|
72
|
+
- Изменять данные о пользователе: имя, описание профиля и другое.
|
73
|
+
- Изменять имя пользователя и пароль.
|
74
|
+
- Запрашивать информацию о подписчиках.
|
75
|
+
- Запрашивать некоторую информацию о пользователе (количество подписчиков и другое).
|
76
|
+
- Голосовать.
|
77
|
+
- Подписываться и отписываться.
|
78
|
+
- Лайкать и дизлайкать.
|
79
|
+
- Твиттить, ретвиттить с изображением и без.
|
80
|
+
- Закреплять твиты.
|
81
|
+
- Запрашивать твиты пользователей.
|
82
|
+
- Удалять твиты.
|
83
|
+
- И другое.
|
84
|
+
|
85
|
+
#### Статус аккаунта
|
86
|
+
После любого взаимодействия с Twitter устанавливается статус аккаунта:
|
87
|
+
- `BAD_TOKEN` - Неверный токен.
|
88
|
+
- `UNKNOWN` - Статус аккаунта не установлен.
|
89
|
+
- `SUSPENDED` - Действие учетной записи приостановлено (бан).
|
90
|
+
- `LOCKED` - Учетная запись заморожена (лок) (требуется прохождение капчи).
|
91
|
+
- `GOOD` - Аккаунт в порядке.
|
92
|
+
|
93
|
+
Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
|
94
|
+
Например, простой запрос данных об аккаунте честно вернет данные, даже если ваш аккаунт заморожен.
|
95
|
+
|
96
|
+
Для достоверной установки статуса аккаунта используйте метод `establish_status()`
|
97
|
+
|
98
|
+
### Примеры работы
|
99
|
+
Запрос информации о пользователе:
|
100
|
+
```python
|
101
|
+
# Запрос информации о текущем пользователе:
|
102
|
+
me = await twitter_client.request_user_data()
|
103
|
+
print(f"[{account.short_auth_token}] {me}")
|
104
|
+
print(f"Аккаунт создан: {me.created_at}")
|
105
|
+
print(f"Following (подписан ты): {me.followings_count}")
|
106
|
+
print(f"Followers (подписаны на тебя): {me.followers_count}")
|
107
|
+
print(f"Прочая информация: {me.raw_data}")
|
108
|
+
|
109
|
+
# Запрос информации об ином пользователе:
|
110
|
+
elonmusk = await twitter.request_user_data("@elonmusk")
|
111
|
+
print(elonmusk)
|
112
|
+
```
|
113
|
+
|
114
|
+
Смена имени пользователя и пароля:
|
115
|
+
```python
|
116
|
+
account = twitter.Account("auth_token", password="password")
|
117
|
+
...
|
118
|
+
await twitter_client.change_username("new_username")
|
119
|
+
await twitter_client.request_user_data()
|
120
|
+
print(f"New username: {account.data.username}")
|
121
|
+
|
122
|
+
await twitter_client.change_password("new_password")
|
123
|
+
print(f"New password: {account.password}")
|
124
|
+
print(f"New auth_token: {account.auth_token}")
|
125
|
+
```
|
126
|
+
|
127
|
+
Смена данных профиля:
|
128
|
+
```python
|
129
|
+
await twitter_client.update_birthdate(day=1, month=12, year=2000)
|
130
|
+
await twitter_client.update_profile( # Locks account!
|
131
|
+
name="New Name",
|
132
|
+
description="New description",
|
133
|
+
location="New York",
|
134
|
+
website="https://github.com/alenkimov/better_automation",
|
135
|
+
)
|
136
|
+
```
|
137
|
+
|
138
|
+
Загрузка изображений и смена аватара и баннера:
|
139
|
+
```python
|
140
|
+
image = open(f"image.png", "rb").read()
|
141
|
+
media_id = await twitter_client.upload_image(image)
|
142
|
+
avatar_image_url = await twitter_client.update_profile_avatar(media_id)
|
143
|
+
banner_image_url = await twitter_client.update_profile_banner(media_id)
|
144
|
+
```
|
145
|
+
|
146
|
+
Привязка сервиса (приложения):
|
147
|
+
|
148
|
+
```python
|
149
|
+
# Изучите запросы сервиса и найдите подобные данные для авторизации (привязки):
|
150
|
+
bind_data = {
|
151
|
+
'response_type': 'code',
|
152
|
+
'client_id': 'TjFVQm52ZDFGWEtNT0tKaktaSWU6MTpjaQ',
|
153
|
+
'redirect_uri': 'https://waitlist.lens.xyz/tw/',
|
154
|
+
'scope': 'users.read tweet.read offline.access',
|
155
|
+
'state': 'state', # Может быть как статичным, так и динамическим.
|
156
|
+
'code_challenge': 'challenge',
|
157
|
+
'code_challenge_method': 'plain'
|
158
|
+
}
|
159
|
+
|
160
|
+
bind_code = await twitter_client.oauth_2(**bind_data)
|
161
|
+
# Передайте код авторизации (привязки) сервису.
|
162
|
+
# Сервис также может потребовать state, если он динамический.
|
163
|
+
```
|
164
|
+
|
165
|
+
Отправка сообщения:
|
166
|
+
```python
|
167
|
+
bro = await twitter_client.request_user_data("@username")
|
168
|
+
await twitter_client.send_message(bro.id, "I love you!")
|
169
|
+
```
|
170
|
+
|
171
|
+
Запрос входящих сообщений:
|
172
|
+
```python
|
173
|
+
messages = await twitter_client.request_messages()
|
174
|
+
for message in messages:
|
175
|
+
message_data = message["message_data"]
|
176
|
+
recipient_id = message_data["recipient_id"]
|
177
|
+
sender_id = message_data["sender_id"]
|
178
|
+
text = message_data["text"]
|
179
|
+
print(f"[id {sender_id}] -> [id {recipient_id}]: {text}")
|
180
|
+
```
|
181
|
+
|
182
|
+
Другие методы:
|
183
|
+
```python
|
184
|
+
# Выражение любви через твит
|
185
|
+
tweet_id = await twitter_client.tweet("I love YOU! !!!!1!1")
|
186
|
+
print(f"Любовь выражена! Tweet id: {tweet_id}")
|
187
|
+
|
188
|
+
print(f"Tweet is pined: {await twitter_client.pin_tweet(tweet_id)}")
|
189
|
+
|
190
|
+
# Лайк
|
191
|
+
print(f"Tweet {tweet_id} is liked: {await twitter_client.like(tweet_id)}")
|
192
|
+
|
193
|
+
# Репост (ретвит)
|
194
|
+
print(f"Tweet {tweet_id} is retweeted. Tweet id: {await twitter_client.repost(tweet_id)}")
|
195
|
+
|
196
|
+
# Коммент (реплай)
|
197
|
+
print(f"Tweet {tweet_id} is replied. Reply id: {await twitter_client.reply(tweet_id, 'tem razão')}")
|
198
|
+
|
199
|
+
# Подписываемся на Илона Маска
|
200
|
+
print(f"@{elonmusk.username} is followed: {await twitter_client.follow(elonmusk.id)}")
|
201
|
+
|
202
|
+
# Отписываемся от Илона Маска
|
203
|
+
print(f"@{elonmusk.username} is unfollowed: {await twitter_client.unfollow(elonmusk.id)}")
|
204
|
+
|
205
|
+
tweet_url = 'https://twitter.com/CreamIce_Cone/status/1691735090529976489'
|
206
|
+
# Цитата (Quote tweet)
|
207
|
+
quote_tweet_id = await twitter_client.quote(tweet_url, 'oh....')
|
208
|
+
print(f"Quoted! Tweet id: {quote_tweet_id}")
|
209
|
+
|
210
|
+
# Запрашиваем первых трех подписчиков
|
211
|
+
# (Параметр count по каким-то причинам работает некорректно)
|
212
|
+
followers = await twitter_client.request_followers(count=20)
|
213
|
+
print("Твои подписчики:")
|
214
|
+
for follower in followers:
|
215
|
+
print(follower)
|
216
|
+
```
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# Tweepy-self
|
2
|
+
[](https://t.me/cum_insider)
|
3
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
4
|
+
[](https://pypi.python.org/pypi/tweepy-self)
|
5
|
+
|
6
|
+
A modern, easy to use, feature-rich, and async ready API wrapper for Twitter's user API written in Python.
|
7
|
+
|
8
|
+
- Docs (soon)
|
9
|
+
|
10
|
+
More libraries of the family:
|
11
|
+
- [better-web3](https://github.com/alenkimov/better_web3)
|
12
|
+
- [better-proxy](https://github.com/alenkimov/better_proxy)
|
13
|
+
- [better-automation](https://github.com/alenkimov/better_automation)
|
14
|
+
|
15
|
+
Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
|
16
|
+
|
17
|
+
## Key Features
|
18
|
+
- Modern Pythonic API using async and await.
|
19
|
+
- Prevents user account automation detection.
|
20
|
+
|
21
|
+
## Installing
|
22
|
+
```bash
|
23
|
+
pip install tweepy-self
|
24
|
+
```
|
25
|
+
|
26
|
+
## Example
|
27
|
+
```python
|
28
|
+
import asyncio
|
29
|
+
import twitter
|
30
|
+
|
31
|
+
account = twitter.Account(auth_token="auth_token")
|
32
|
+
|
33
|
+
async def main():
|
34
|
+
async with twitter.Client(account) as twitter_client:
|
35
|
+
await twitter_client.tweet("Hello, tweepy-self! <3")
|
36
|
+
|
37
|
+
asyncio.run(main())
|
38
|
+
```
|
39
|
+
|
40
|
+
## More
|
41
|
+
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
|
42
|
+
|
43
|
+
## Документация (устаревшая)
|
44
|
+
`Код ушел немного дальше, чем эта документация.`
|
45
|
+
|
46
|
+
Библиотека позволяет работать с неофициальным API Twitter, а именно:
|
47
|
+
- Логин
|
48
|
+
- Анлок
|
49
|
+
- Привязывать сервисы (приложения).
|
50
|
+
- Устанавливать статус аккаунта (бан, лок).
|
51
|
+
- Загружать изображения на сервер и изменять баннер и аватарку.
|
52
|
+
- Изменять данные о пользователе: имя, описание профиля и другое.
|
53
|
+
- Изменять имя пользователя и пароль.
|
54
|
+
- Запрашивать информацию о подписчиках.
|
55
|
+
- Запрашивать некоторую информацию о пользователе (количество подписчиков и другое).
|
56
|
+
- Голосовать.
|
57
|
+
- Подписываться и отписываться.
|
58
|
+
- Лайкать и дизлайкать.
|
59
|
+
- Твиттить, ретвиттить с изображением и без.
|
60
|
+
- Закреплять твиты.
|
61
|
+
- Запрашивать твиты пользователей.
|
62
|
+
- Удалять твиты.
|
63
|
+
- И другое.
|
64
|
+
|
65
|
+
#### Статус аккаунта
|
66
|
+
После любого взаимодействия с Twitter устанавливается статус аккаунта:
|
67
|
+
- `BAD_TOKEN` - Неверный токен.
|
68
|
+
- `UNKNOWN` - Статус аккаунта не установлен.
|
69
|
+
- `SUSPENDED` - Действие учетной записи приостановлено (бан).
|
70
|
+
- `LOCKED` - Учетная запись заморожена (лок) (требуется прохождение капчи).
|
71
|
+
- `GOOD` - Аккаунт в порядке.
|
72
|
+
|
73
|
+
Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
|
74
|
+
Например, простой запрос данных об аккаунте честно вернет данные, даже если ваш аккаунт заморожен.
|
75
|
+
|
76
|
+
Для достоверной установки статуса аккаунта используйте метод `establish_status()`
|
77
|
+
|
78
|
+
### Примеры работы
|
79
|
+
Запрос информации о пользователе:
|
80
|
+
```python
|
81
|
+
# Запрос информации о текущем пользователе:
|
82
|
+
me = await twitter_client.request_user_data()
|
83
|
+
print(f"[{account.short_auth_token}] {me}")
|
84
|
+
print(f"Аккаунт создан: {me.created_at}")
|
85
|
+
print(f"Following (подписан ты): {me.followings_count}")
|
86
|
+
print(f"Followers (подписаны на тебя): {me.followers_count}")
|
87
|
+
print(f"Прочая информация: {me.raw_data}")
|
88
|
+
|
89
|
+
# Запрос информации об ином пользователе:
|
90
|
+
elonmusk = await twitter.request_user_data("@elonmusk")
|
91
|
+
print(elonmusk)
|
92
|
+
```
|
93
|
+
|
94
|
+
Смена имени пользователя и пароля:
|
95
|
+
```python
|
96
|
+
account = twitter.Account("auth_token", password="password")
|
97
|
+
...
|
98
|
+
await twitter_client.change_username("new_username")
|
99
|
+
await twitter_client.request_user_data()
|
100
|
+
print(f"New username: {account.data.username}")
|
101
|
+
|
102
|
+
await twitter_client.change_password("new_password")
|
103
|
+
print(f"New password: {account.password}")
|
104
|
+
print(f"New auth_token: {account.auth_token}")
|
105
|
+
```
|
106
|
+
|
107
|
+
Смена данных профиля:
|
108
|
+
```python
|
109
|
+
await twitter_client.update_birthdate(day=1, month=12, year=2000)
|
110
|
+
await twitter_client.update_profile( # Locks account!
|
111
|
+
name="New Name",
|
112
|
+
description="New description",
|
113
|
+
location="New York",
|
114
|
+
website="https://github.com/alenkimov/better_automation",
|
115
|
+
)
|
116
|
+
```
|
117
|
+
|
118
|
+
Загрузка изображений и смена аватара и баннера:
|
119
|
+
```python
|
120
|
+
image = open(f"image.png", "rb").read()
|
121
|
+
media_id = await twitter_client.upload_image(image)
|
122
|
+
avatar_image_url = await twitter_client.update_profile_avatar(media_id)
|
123
|
+
banner_image_url = await twitter_client.update_profile_banner(media_id)
|
124
|
+
```
|
125
|
+
|
126
|
+
Привязка сервиса (приложения):
|
127
|
+
|
128
|
+
```python
|
129
|
+
# Изучите запросы сервиса и найдите подобные данные для авторизации (привязки):
|
130
|
+
bind_data = {
|
131
|
+
'response_type': 'code',
|
132
|
+
'client_id': 'TjFVQm52ZDFGWEtNT0tKaktaSWU6MTpjaQ',
|
133
|
+
'redirect_uri': 'https://waitlist.lens.xyz/tw/',
|
134
|
+
'scope': 'users.read tweet.read offline.access',
|
135
|
+
'state': 'state', # Может быть как статичным, так и динамическим.
|
136
|
+
'code_challenge': 'challenge',
|
137
|
+
'code_challenge_method': 'plain'
|
138
|
+
}
|
139
|
+
|
140
|
+
bind_code = await twitter_client.oauth_2(**bind_data)
|
141
|
+
# Передайте код авторизации (привязки) сервису.
|
142
|
+
# Сервис также может потребовать state, если он динамический.
|
143
|
+
```
|
144
|
+
|
145
|
+
Отправка сообщения:
|
146
|
+
```python
|
147
|
+
bro = await twitter_client.request_user_data("@username")
|
148
|
+
await twitter_client.send_message(bro.id, "I love you!")
|
149
|
+
```
|
150
|
+
|
151
|
+
Запрос входящих сообщений:
|
152
|
+
```python
|
153
|
+
messages = await twitter_client.request_messages()
|
154
|
+
for message in messages:
|
155
|
+
message_data = message["message_data"]
|
156
|
+
recipient_id = message_data["recipient_id"]
|
157
|
+
sender_id = message_data["sender_id"]
|
158
|
+
text = message_data["text"]
|
159
|
+
print(f"[id {sender_id}] -> [id {recipient_id}]: {text}")
|
160
|
+
```
|
161
|
+
|
162
|
+
Другие методы:
|
163
|
+
```python
|
164
|
+
# Выражение любви через твит
|
165
|
+
tweet_id = await twitter_client.tweet("I love YOU! !!!!1!1")
|
166
|
+
print(f"Любовь выражена! Tweet id: {tweet_id}")
|
167
|
+
|
168
|
+
print(f"Tweet is pined: {await twitter_client.pin_tweet(tweet_id)}")
|
169
|
+
|
170
|
+
# Лайк
|
171
|
+
print(f"Tweet {tweet_id} is liked: {await twitter_client.like(tweet_id)}")
|
172
|
+
|
173
|
+
# Репост (ретвит)
|
174
|
+
print(f"Tweet {tweet_id} is retweeted. Tweet id: {await twitter_client.repost(tweet_id)}")
|
175
|
+
|
176
|
+
# Коммент (реплай)
|
177
|
+
print(f"Tweet {tweet_id} is replied. Reply id: {await twitter_client.reply(tweet_id, 'tem razão')}")
|
178
|
+
|
179
|
+
# Подписываемся на Илона Маска
|
180
|
+
print(f"@{elonmusk.username} is followed: {await twitter_client.follow(elonmusk.id)}")
|
181
|
+
|
182
|
+
# Отписываемся от Илона Маска
|
183
|
+
print(f"@{elonmusk.username} is unfollowed: {await twitter_client.unfollow(elonmusk.id)}")
|
184
|
+
|
185
|
+
tweet_url = 'https://twitter.com/CreamIce_Cone/status/1691735090529976489'
|
186
|
+
# Цитата (Quote tweet)
|
187
|
+
quote_tweet_id = await twitter_client.quote(tweet_url, 'oh....')
|
188
|
+
print(f"Quoted! Tweet id: {quote_tweet_id}")
|
189
|
+
|
190
|
+
# Запрашиваем первых трех подписчиков
|
191
|
+
# (Параметр count по каким-то причинам работает некорректно)
|
192
|
+
followers = await twitter_client.request_followers(count=20)
|
193
|
+
print("Твои подписчики:")
|
194
|
+
for follower in followers:
|
195
|
+
print(follower)
|
196
|
+
```
|
@@ -1,14 +1,21 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "tweepy-self"
|
3
|
-
version = "
|
3
|
+
version = "1.0.0"
|
4
4
|
description = "Twitter (selfbot) for Python!"
|
5
5
|
authors = ["Alen <alen.kimov@gmail.com>"]
|
6
6
|
readme = "README.md"
|
7
|
-
packages = [{include = "
|
7
|
+
packages = [{include = "twitter"}]
|
8
8
|
|
9
9
|
[tool.poetry.dependencies]
|
10
10
|
python = "^3.11"
|
11
|
-
|
11
|
+
curl_cffi = {version = "0.6.0b9", allow-prereleases = true}
|
12
|
+
python3-capsolver = "^0.9"
|
13
|
+
better-proxy = "0.5.0"
|
14
|
+
beautifulsoup4 = "^4"
|
15
|
+
pydantic = "^2"
|
16
|
+
lxml = "^5"
|
17
|
+
pyotp = "^2"
|
18
|
+
yarl = "^1"
|
12
19
|
|
13
20
|
[build-system]
|
14
21
|
requires = ["poetry-core"]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Twitter API Wrapper
|
3
|
+
~~~~~~~~~~~~~~~~~~~
|
4
|
+
|
5
|
+
A basic wrapper for the Twitter user API.
|
6
|
+
"""
|
7
|
+
|
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
|
11
|
+
from . import errors, utils
|
12
|
+
|
13
|
+
__all__ = [
|
14
|
+
"Client",
|
15
|
+
"Account",
|
16
|
+
"AccountStatus",
|
17
|
+
"utils",
|
18
|
+
"errors",
|
19
|
+
"load_accounts_from_file",
|
20
|
+
"extract_accounts_to_file",
|
21
|
+
]
|
22
|
+
|
23
|
+
|
24
|
+
import warnings
|
25
|
+
# HACK: Ignore event loop warnings from curl_cffi
|
26
|
+
warnings.filterwarnings('ignore', module='curl_cffi')
|
@@ -0,0 +1,98 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Sequence, Iterable
|
3
|
+
import enum
|
4
|
+
|
5
|
+
from pydantic import BaseModel, Field
|
6
|
+
import pyotp
|
7
|
+
|
8
|
+
from .utils import hidden_value, load_lines, write_lines
|
9
|
+
|
10
|
+
|
11
|
+
class AccountStatus(enum.StrEnum):
|
12
|
+
BAD_TOKEN = "BAD_TOKEN" # (401) 32
|
13
|
+
UNKNOWN = "UNKNOWN"
|
14
|
+
SUSPENDED = "SUSPENDED" # (403) 64, (200) 141
|
15
|
+
LOCKED = "LOCKED" # (403) 326
|
16
|
+
GOOD = "GOOD"
|
17
|
+
|
18
|
+
def __str__(self):
|
19
|
+
return self.value
|
20
|
+
|
21
|
+
|
22
|
+
class Account(BaseModel):
|
23
|
+
auth_token: str | None = Field(default=None, pattern=r"^[a-f0-9]{40}$")
|
24
|
+
ct0: str | None
|
25
|
+
id: int | None
|
26
|
+
name: str | None
|
27
|
+
username: str | None
|
28
|
+
password: str | None
|
29
|
+
email: str | None
|
30
|
+
key2fa: str | None = Field(default=None, pattern=r"^[a-f0-9]{12}$")
|
31
|
+
backup_code: str | None = Field(default=None, pattern=r"^[A-Z0-9]{16}$")
|
32
|
+
status: AccountStatus = AccountStatus.UNKNOWN
|
33
|
+
|
34
|
+
@property
|
35
|
+
def hidden_auth_token(self) -> str | None:
|
36
|
+
return hidden_value(self.auth_token) if self.auth_token else None
|
37
|
+
|
38
|
+
@property
|
39
|
+
def hidden_password(self) -> str | None:
|
40
|
+
return hidden_value(self.password) if self.password else None
|
41
|
+
|
42
|
+
@property
|
43
|
+
def hidden_key2fa(self) -> str | None:
|
44
|
+
return hidden_value(self.key2fa) if self.key2fa else None
|
45
|
+
|
46
|
+
@property
|
47
|
+
def hidden_backup_code(self) -> str | None:
|
48
|
+
return hidden_value(self.backup_code) if self.backup_code else None
|
49
|
+
|
50
|
+
def __repr__(self):
|
51
|
+
return f"{self.__class__.__name__}(auth_token={self.hidden_auth_token}, username={self.username})"
|
52
|
+
|
53
|
+
def __str__(self):
|
54
|
+
return self.hidden_auth_token
|
55
|
+
|
56
|
+
def get_2fa_code(self) -> str | None:
|
57
|
+
if not self.key2fa:
|
58
|
+
raise ValueError("No key2fa")
|
59
|
+
|
60
|
+
return str(pyotp.TOTP(self.key2fa).now())
|
61
|
+
|
62
|
+
|
63
|
+
def load_accounts_from_file(
|
64
|
+
filepath: Path | str,
|
65
|
+
*,
|
66
|
+
separator: str = ":",
|
67
|
+
fields: Sequence[str] = ("auth_token", "password", "email", "username"),
|
68
|
+
) -> list[Account]:
|
69
|
+
"""
|
70
|
+
:param filepath: Путь до файла с данными об аккаунтах.
|
71
|
+
:param separator: Разделитель между данными в строке.
|
72
|
+
:param fields: Кортеж, содержащий имена полей в порядке их появления в строке.
|
73
|
+
:return: Список Twitter аккаунтов.
|
74
|
+
"""
|
75
|
+
accounts = []
|
76
|
+
for line in load_lines(filepath):
|
77
|
+
data = dict(zip(fields, line.split(separator)))
|
78
|
+
data.update({key: None for key in data if not data[key]})
|
79
|
+
accounts.append(Account(**data))
|
80
|
+
return accounts
|
81
|
+
|
82
|
+
|
83
|
+
def extract_accounts_to_file(
|
84
|
+
filepath: Path | str,
|
85
|
+
accounts: Iterable[Account],
|
86
|
+
*,
|
87
|
+
separator: str = ":",
|
88
|
+
fields: Sequence[str] = ("auth_token", "password", "email", "username"),
|
89
|
+
):
|
90
|
+
lines = []
|
91
|
+
for account in accounts:
|
92
|
+
account_data = []
|
93
|
+
for field_name in fields:
|
94
|
+
field = getattr(account, field_name)
|
95
|
+
field = field if field is not None else ""
|
96
|
+
account_data.append(field)
|
97
|
+
lines.append(separator.join(account_data))
|
98
|
+
write_lines(filepath, lines)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from .session import BaseAsyncSession
|
2
|
+
|
3
|
+
|
4
|
+
class BaseClient:
|
5
|
+
_DEFAULT_HEADERS = None
|
6
|
+
|
7
|
+
def __init__(self, **session_kwargs):
|
8
|
+
self._session = BaseAsyncSession(
|
9
|
+
headers=session_kwargs.pop("headers", None) or self._DEFAULT_HEADERS,
|
10
|
+
**session_kwargs,
|
11
|
+
)
|
12
|
+
|
13
|
+
async def __aenter__(self):
|
14
|
+
return self
|
15
|
+
|
16
|
+
async def __aexit__(self, *args):
|
17
|
+
self.close()
|
18
|
+
|
19
|
+
def close(self):
|
20
|
+
self._session.close()
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from curl_cffi import requests
|
2
|
+
from better_proxy import Proxy
|
3
|
+
|
4
|
+
|
5
|
+
class BaseAsyncSession(requests.AsyncSession):
|
6
|
+
"""
|
7
|
+
Базовая асинхронная сессия:
|
8
|
+
- Принимает прокси в формате URL и better-proxy.
|
9
|
+
- Отключает верификацию SSL сертификатов по умолчанию.
|
10
|
+
- По умолчанию устанавливает версию браузера chrome120.
|
11
|
+
- По умолчанию устанавливает user-agent под версию браузера chrome120.
|
12
|
+
"""
|
13
|
+
proxy: Proxy | None
|
14
|
+
DEFAULT_HEADERS = {
|
15
|
+
"accept": "*/*",
|
16
|
+
"accept-language": "en-US,en",
|
17
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
18
|
+
"sec-ch-ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
|
19
|
+
"sec-ch-ua-platform": '"Windows"',
|
20
|
+
"sec-ch-ua-mobile": "?0",
|
21
|
+
"sec-fetch-dest": "empty",
|
22
|
+
"sec-fetch-mode": "cors",
|
23
|
+
"sec-fetch-site": "same-origin",
|
24
|
+
"connection": "keep-alive",
|
25
|
+
}
|
26
|
+
DEFAULT_IMPERSONATE = requests.BrowserType.chrome120
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
proxy: str | Proxy = None,
|
31
|
+
**session_kwargs,
|
32
|
+
):
|
33
|
+
self._proxy = None
|
34
|
+
headers = session_kwargs["headers"] = session_kwargs.get("headers") or {}
|
35
|
+
headers.update(self.DEFAULT_HEADERS)
|
36
|
+
session_kwargs["impersonate"] = session_kwargs.get("impersonate") or self.DEFAULT_IMPERSONATE
|
37
|
+
session_kwargs["verify"] = session_kwargs.get("verify", False)
|
38
|
+
super().__init__(**session_kwargs)
|
39
|
+
self.proxy = proxy
|
40
|
+
|
41
|
+
@property
|
42
|
+
def user_agent(self) -> str:
|
43
|
+
return self.headers["user-agent"]
|
44
|
+
|
45
|
+
@property
|
46
|
+
def proxy(self) -> Proxy | None:
|
47
|
+
return self._proxy
|
48
|
+
|
49
|
+
@proxy.setter
|
50
|
+
def proxy(self, proxy: str | Proxy | None):
|
51
|
+
if not proxy:
|
52
|
+
self.proxies = {}
|
53
|
+
return
|
54
|
+
|
55
|
+
self._proxy = Proxy.from_str(proxy) if proxy else None
|
56
|
+
self.proxies = {"http": self._proxy.as_url, "https": self._proxy.as_url}
|