tweepy-self 1.9.0__py3-none-any.whl → 1.10.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.
twitter/errors.py CHANGED
@@ -20,7 +20,6 @@ __all__ = [
20
20
  ]
21
21
 
22
22
 
23
- # TODO Возвращать аккаунт в теле исключения
24
23
  class TwitterException(Exception):
25
24
  pass
26
25
 
@@ -62,12 +61,17 @@ class HTTPException(TwitterException):
62
61
  self.api_codes: list[int] = []
63
62
  self.api_messages: list[str] = []
64
63
  self.detail: str | None = None
64
+ self.html: str | None = None
65
65
 
66
66
  # Если ответ — строка, то это html
67
67
  if isinstance(data, str):
68
- exception_message = (
69
- f"(response status: {response.status_code}) HTML Response:\n{data}"
70
- )
68
+ if not data:
69
+ exception_message = (
70
+ f"(response status: {response.status_code}) Empty response body."
71
+ )
72
+ else:
73
+ self.html = data
74
+ exception_message = f"(response status: {response.status_code}) HTML Response:\n{self.html}"
71
75
  if response.status_code == 429:
72
76
  exception_message = (
73
77
  f"(response status: {response.status_code}) Rate limit exceeded."
@@ -147,7 +151,9 @@ class BadAccount(TwitterException):
147
151
 
148
152
  class BadToken(BadAccount):
149
153
  def __init__(self, http_exception: "HTTPException", account: Account):
150
- exception_message = "Bad Twitter account's auth_token."
154
+ exception_message = (
155
+ "Bad Twitter account's auth_token. Relogin to get new token."
156
+ )
151
157
  super().__init__(http_exception, account, exception_message)
152
158
 
153
159
 
@@ -155,14 +161,14 @@ class Locked(BadAccount):
155
161
  def __init__(self, http_exception: "HTTPException", account: Account):
156
162
  exception_message = (
157
163
  f"Twitter account is locked."
158
- f" Set CapSolver API key (capsolver_api_key) to autounlock."
164
+ f" Set CapSolver API key (capsolver_api_key) to auto-unlock."
159
165
  )
160
166
  super().__init__(http_exception, account, exception_message)
161
167
 
162
168
 
163
169
  class ConsentLocked(BadAccount):
164
170
  def __init__(self, http_exception: "HTTPException", account: Account):
165
- exception_message = f"Twitter account is consent locked. Relogin to unlock."
171
+ exception_message = f"Twitter account is locked."
166
172
  super().__init__(http_exception, account, exception_message)
167
173
 
168
174
 
twitter/models.py CHANGED
@@ -1,50 +1,40 @@
1
- from typing import Optional
1
+ from typing import Optional, Any
2
2
  from datetime import datetime, timedelta
3
3
 
4
- from pydantic import BaseModel
4
+ from pydantic import BaseModel, Field, field_validator
5
5
 
6
6
  from .utils import to_datetime, tweet_url
7
7
 
8
8
 
9
9
  class Image(BaseModel):
10
- type: str
11
- width: int
12
- height: int
10
+ type: str = Field(..., alias="image_type")
11
+ width: int = Field(..., alias="w")
12
+ height: int = Field(..., alias="h")
13
13
 
14
14
 
15
15
  class Media(BaseModel):
16
- id: int
16
+ id: int = Field(..., alias="media_id")
17
17
  image: Image
18
18
  size: int
19
- expires_at: datetime
19
+ expires_at: datetime = Field(..., alias="expires_after_secs")
20
+
21
+ @field_validator("expires_at", mode="before")
22
+ @classmethod
23
+ def set_expires_at(cls, v):
24
+ return datetime.now() + timedelta(seconds=v)
20
25
 
21
26
  def __str__(self):
22
27
  return str(self.id)
23
28
 
24
- @classmethod
25
- def from_raw_data(cls, data: dict):
26
- expires_at = datetime.now() + timedelta(seconds=data["expires_after_secs"])
27
- values = {
28
- "image": {
29
- "type": data["image"]["image_type"],
30
- "width": data["image"]["w"],
31
- "height": data["image"]["h"],
32
- },
33
- "size": data["size"],
34
- "id": data["media_id"],
35
- "expires_at": expires_at,
36
- }
37
- return cls(**values)
38
-
39
29
 
40
30
  class User(BaseModel):
41
31
  # fmt: off
42
32
  id: int | None = None
43
33
  username: str | None = None
44
- name: str | None = None
34
+ name: str | None = None # 50
45
35
  created_at: datetime | None = None
46
- description: str | None = None
47
- location: str | None = None
36
+ description: str | None = None # 160
37
+ location: str | None = None # 30
48
38
  followers_count: int | None = None
49
39
  friends_count: int | None = None
50
40
  raw_data: dict | None = None
@@ -163,3 +153,24 @@ class Tweet(BaseModel):
163
153
  "raw_data": data,
164
154
  }
165
155
  return cls(**values)
156
+
157
+
158
+ class Subtask(BaseModel):
159
+ id: str
160
+ primary_text: Optional[str] = None
161
+ secondary_text: Optional[str] = None
162
+ detail_text: Optional[str] = None
163
+ raw_data: dict
164
+
165
+ @classmethod
166
+ def from_raw_data(cls, data: dict) -> "Subtask":
167
+ task = {"id": data["subtask_id"]}
168
+ if enter_text := data.get("enter_text"):
169
+ if header := enter_text.get("header"):
170
+ if primary_text := header.get("primary_text"):
171
+ task["primary_text"] = primary_text["text"]
172
+ if secondary_text := header.get("secondary_text"):
173
+ task["secondary_text"] = secondary_text["text"]
174
+ if detail_text := header.get("detail_text"):
175
+ task["detail_text"] = detail_text["text"]
176
+ return cls(**task, raw_data=data)
twitter/utils/html.py CHANGED
@@ -21,9 +21,11 @@ def parse_oauth_html(html: str) -> tuple[str | None, str | None, str | None]:
21
21
  return authenticity_token, redirect_url, redirect_after_login_url
22
22
 
23
23
 
24
- def parse_unlock_html(html: str) -> tuple[str | None, str | None, bool, bool, bool]:
24
+ def parse_unlock_html(
25
+ html: str,
26
+ ) -> tuple[str | None, str | None, bool, bool, bool, bool]:
25
27
  """
26
- :return: authenticity_token, assignment_token, needs_unlock, start_button, finish_button
28
+ :return: authenticity_token, assignment_token, needs_unlock, start_button, finish_button, delete_button
27
29
  """
28
30
  soup = BeautifulSoup(html, "lxml")
29
31
  authenticity_token_element = soup.find("input", {"name": "authenticity_token"})
@@ -38,10 +40,12 @@ def parse_unlock_html(html: str) -> tuple[str | None, str | None, bool, bool, bo
38
40
  needs_unlock = bool(verification_string)
39
41
  start_button = bool(soup.find("input", value="Start"))
40
42
  finish_button = bool(soup.find("input", value="Continue to X"))
43
+ delete_button = bool(soup.find("input", value="Delete"))
41
44
  return (
42
45
  authenticity_token,
43
46
  assignment_token,
44
47
  needs_unlock,
45
48
  start_button,
46
49
  finish_button,
50
+ delete_button,
47
51
  )
@@ -1,225 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: tweepy-self
3
- Version: 1.9.0
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.0b9)
16
- Requires-Dist: loguru (>=0.7,<0.8)
17
- Requires-Dist: lxml (>=5,<6)
18
- Requires-Dist: pydantic (>=1)
19
- Requires-Dist: pyotp (>=2,<3)
20
- Requires-Dist: requests (>=2,<3)
21
- Requires-Dist: tenacity (>=8,<9)
22
- Requires-Dist: yarl (>=1,<2)
23
- Project-URL: Repository, https://github.com/alenkimov/tweepy-self
24
- Project-URL: Source, https://github.com/alenkimov/tweepy-self
25
- Description-Content-Type: text/markdown
26
-
27
- # Tweepy-self
28
- [![Telegram channel](https://img.shields.io/endpoint?url=https://runkit.io/damiankrawczyk/telegram-badge/branches/master?url=https://t.me/cum_insider)](https://t.me/cum_insider)
29
- [![PyPI version info](https://img.shields.io/pypi/v/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
30
- [![PyPI supported Python versions](https://img.shields.io/pypi/pyversions/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
31
- [![PyPI downloads per month](https://img.shields.io/pypi/dm/tweepy-self.svg)](https://pypi.python.org/pypi/tweepy-self)
32
-
33
- A modern, easy to use, feature-rich, and async ready API wrapper for Twitter's user API written in Python.
34
-
35
- - Docs (soon)
36
-
37
- More libraries of the family:
38
- - [better-proxy](https://github.com/alenkimov/better_proxy)
39
- - [better-web3](https://github.com/alenkimov/better_web3)
40
-
41
- Отдельное спасибо [Кузнице Ботов](https://t.me/bots_forge) за код для авторизации и разморозки! Подписывайтесь на их Telegram :)
42
-
43
- ## Key Features
44
- - Modern Pythonic API using async and await.
45
- - Prevents user account automation detection.
46
-
47
- ## Installing
48
- ```bash
49
- pip install tweepy-self
50
- ```
51
-
52
- ## Example
53
- ```python
54
- import asyncio
55
- import twitter
56
-
57
- account = twitter.Account(auth_token="auth_token")
58
-
59
- async def main():
60
- async with twitter.Client(account) as twitter_client:
61
- await twitter_client.tweet("Hello, tweepy-self! <3")
62
-
63
- asyncio.run(main())
64
- ```
65
-
66
- ## More
67
- Automating user accounts is against the Twitter ToS. This library is a proof of concept and I cannot recommend using it. Do so at your own risk
68
-
69
- ## Документация (устаревшая)
70
- `Код ушел немного дальше, чем эта документация.`
71
-
72
- Библиотека позволяет работать с неофициальным API Twitter, а именно:
73
- - Логин
74
- - Анлок
75
- - Привязывать сервисы (приложения).
76
- - Устанавливать статус аккаунта (бан, лок).
77
- - Загружать изображения на сервер и изменять баннер и аватарку.
78
- - Изменять данные о пользователе: имя, описание профиля и другое.
79
- - Изменять имя пользователя и пароль.
80
- - Запрашивать информацию о подписчиках.
81
- - Запрашивать некоторую информацию о пользователе (количество подписчиков и другое).
82
- - Голосовать.
83
- - Подписываться и отписываться.
84
- - Лайкать и дизлайкать.
85
- - Твиттить, ретвиттить с изображением и без.
86
- - Закреплять твиты.
87
- - Запрашивать твиты пользователей.
88
- - Удалять твиты.
89
- - И другое.
90
-
91
- #### Статус аккаунта
92
- После любого взаимодействия с Twitter устанавливается статус аккаунта:
93
- - `BAD_TOKEN` - Неверный токен.
94
- - `UNKNOWN` - Статус аккаунта не установлен.
95
- - `SUSPENDED` - Действие учетной записи приостановлено (бан).
96
- - `LOCKED` - Учетная запись заморожена (лок) (требуется прохождение капчи).
97
- - `GOOD` - Аккаунт в порядке.
98
-
99
- Не каждое взаимодействие с Twitter достоверно определяет статус аккаунта.
100
- Например, простой запрос данных об аккаунте честно вернет данные, даже если ваш аккаунт заморожен.
101
-
102
- Для достоверной установки статуса аккаунта используйте метод `establish_status()`
103
-
104
- ### Примеры работы
105
- Запрос информации о пользователе:
106
-
107
- ```python
108
- # Запрос информации о текущем пользователе:
109
- me = await twitter_client.request_user()
110
- print(f"[{account.short_auth_token}] {me}")
111
- print(f"Аккаунт создан: {me.created_at}")
112
- print(f"Following (подписан ты): {me.followings_count}")
113
- print(f"Followers (подписаны на тебя): {me.followers_count}")
114
- print(f"Прочая информация: {me.raw_data}")
115
-
116
- # Запрос информации об ином пользователе:
117
- elonmusk = await twitter.request_user("@elonmusk")
118
- print(elonmusk)
119
- ```
120
-
121
- Смена имени пользователя и пароля:
122
-
123
- ```python
124
- account = twitter.Account("auth_token", password="password")
125
- ...
126
- await twitter_client.change_username("new_username")
127
- await twitter_client.request_user()
128
- print(f"New username: {account.username}")
129
-
130
- await twitter_client.change_password("new_password")
131
- print(f"New password: {account.password}")
132
- print(f"New auth_token: {account.auth_token}")
133
- ```
134
-
135
- Смена данных профиля:
136
- ```python
137
- await twitter_client.update_birthdate(day=1, month=12, year=2000)
138
- await twitter_client.update_profile( # Locks account!
139
- name="New Name",
140
- description="New description",
141
- location="New York",
142
- website="https://github.com/alenkimov/better_automation",
143
- )
144
- ```
145
-
146
- Загрузка изображений и смена аватара и баннера:
147
- ```python
148
- image = open(f"image.png", "rb").read()
149
- media_id = await twitter_client.upload_image(image)
150
- avatar_image_url = await twitter_client.update_profile_avatar(media_id)
151
- banner_image_url = await twitter_client.update_profile_banner(media_id)
152
- ```
153
-
154
- Привязка сервиса (приложения):
155
-
156
- ```python
157
- # Изучите запросы сервиса и найдите подобные данные для авторизации (привязки):
158
- bind_data = {
159
- 'response_type': 'code',
160
- 'client_id': 'TjFVQm52ZDFGWEtNT0tKaktaSWU6MTpjaQ',
161
- 'redirect_uri': 'https://waitlist.lens.xyz/tw/',
162
- 'scope': 'users.read tweet.read offline.access',
163
- 'state': 'state', # Может быть как статичным, так и динамическим.
164
- 'code_challenge': 'challenge',
165
- 'code_challenge_method': 'plain'
166
- }
167
-
168
- bind_code = await twitter_client.oauth_2(**bind_data)
169
- # Передайте код авторизации (привязки) сервису.
170
- # Сервис также может потребовать state, если он динамический.
171
- ```
172
-
173
- Отправка сообщения:
174
-
175
- ```python
176
- bro = await twitter_client.request_user("@username")
177
- await twitter_client.send_message(bro.id, "I love you!")
178
- ```
179
-
180
- Запрос входящих сообщений:
181
- ```python
182
- messages = await twitter_client.request_messages()
183
- for message in messages:
184
- message_data = message["message_data"]
185
- recipient_id = message_data["recipient_id"]
186
- sender_id = message_data["sender_id"]
187
- text = message_data["text"]
188
- print(f"[id {sender_id}] -> [id {recipient_id}]: {text}")
189
- ```
190
-
191
- Другие методы:
192
- ```python
193
- # Выражение любви через твит
194
- tweet_id = await twitter_client.tweet("I love YOU! !!!!1!1")
195
- print(f"Любовь выражена! Tweet id: {tweet_id}")
196
-
197
- print(f"Tweet is pined: {await twitter_client.pin_tweet(tweet_id)}")
198
-
199
- # Лайк
200
- print(f"Tweet {tweet_id} is liked: {await twitter_client.like(tweet_id)}")
201
-
202
- # Репост (ретвит)
203
- print(f"Tweet {tweet_id} is retweeted. Tweet id: {await twitter_client.repost(tweet_id)}")
204
-
205
- # Коммент (реплай)
206
- print(f"Tweet {tweet_id} is replied. Reply id: {await twitter_client.reply(tweet_id, 'tem razão')}")
207
-
208
- # Подписываемся на Илона Маска
209
- print(f"@{elonmusk.username} is followed: {await twitter_client.follow(elonmusk.id)}")
210
-
211
- # Отписываемся от Илона Маска
212
- print(f"@{elonmusk.username} is unfollowed: {await twitter_client.unfollow(elonmusk.id)}")
213
-
214
- tweet_url = 'https://twitter.com/CreamIce_Cone/status/1691735090529976489'
215
- # Цитата (Quote tweet)
216
- quote_tweet_id = await twitter_client.quote(tweet_url, 'oh....')
217
- print(f"Quoted! Tweet id: {quote_tweet_id}")
218
-
219
- # Запрашиваем первых трех подписчиков
220
- # (Параметр count по каким-то причинам работает некорректно)
221
- followers = await twitter_client.request_followers(count=20)
222
- print("Твои подписчики:")
223
- for follower in followers:
224
- print(follower)
225
- ```