tweepy-self 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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,15 @@
1
+ twitter/__init__.py,sha256=9KthivNBM-ooKWzAMCBOtC7HuwHLGyhrLeFvDf50vxg,581
2
+ twitter/account.py,sha256=Vb9MCRxIQRODnefchXg3m0BGzQ1mEJnaL3xbv29zGTw,3294
3
+ twitter/base/__init__.py,sha256=x0EHKv4q_FI6xEq2nL4V9s8P6VWr6IaHTqdH9sXB5d8,133
4
+ twitter/base/client.py,sha256=7byb0Psai-dvg_ww6Y7uyE2hV1pfTU653hFgVdRiqXo,478
5
+ twitter/base/session.py,sha256=6-gLhdSCaTCd_zv3YgUtVRGbfiAawXuDRBoo7s5bGSs,2234
6
+ twitter/client.py,sha256=bIwoNFTcznAzth76Xcio8eXoe7seMQyEQpJ3fnMJqGs,53114
7
+ twitter/errors.py,sha256=U6kGyNp_5tEq-RwxLjm61muJLEp5BYBq9vrPBkCxr_g,4088
8
+ twitter/models.py,sha256=3-Lft160msCqOjRPubOmxMqWUkmjlTSzHSGsvZK91nU,1817
9
+ twitter/utils/__init__.py,sha256=pyhQXwTdp0HFwV_UNF4dTyklLD9RtaefA16SrQXeNlg,589
10
+ twitter/utils/file.py,sha256=-6n8I8KWDlntfciJJsfIeOi0gmqoHRIe1ldIx1ynGUE,1118
11
+ twitter/utils/html.py,sha256=Cs55MxVyZLSKiCEj11ALUrnCW9ADZ4CEDCE0gKESzO0,1627
12
+ twitter/utils/other.py,sha256=4NaGd2CIJVrDiW17shcrDlJRqFkQNbBSTiiH7kNWcww,559
13
+ tweepy_self-1.0.0.dist-info/METADATA,sha256=RMRe1Jgbsd9csQGYeA1mhRd4d-qszaV7d46th6cw4UQ,9139
14
+ tweepy_self-1.0.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
15
+ tweepy_self-1.0.0.dist-info/RECORD,,
twitter/__init__.py ADDED
@@ -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')
twitter/account.py ADDED
@@ -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
+ ]
twitter/base/client.py ADDED
@@ -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}