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.
@@ -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
+ [![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)
23
+ [![PyPI version info](https://img.shields.io/pypi/v/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
24
+ [![PyPI supported Python versions](https://img.shields.io/pypi/pyversions/tweepy-self.svg)](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
+ [![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)
3
+ [![PyPI version info](https://img.shields.io/pypi/v/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
4
+ [![PyPI supported Python versions](https://img.shields.io/pypi/pyversions/tweepy-self.svg)](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 = "0.1.0"
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 = "tweepy-self"}]
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,7 @@
1
+ from .client import BaseClient
2
+ from .session import BaseAsyncSession
3
+
4
+ __all__ = [
5
+ "BaseClient",
6
+ "BaseAsyncSession",
7
+ ]
@@ -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}