tweepy-self 1.9.0__py3-none-any.whl → 1.10.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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
- ```