tweepy-self 1.11.0__tar.gz → 1.12.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (23) hide show
  1. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/PKG-INFO +3 -3
  2. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/pyproject.toml +3 -3
  3. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/__init__.py +0 -5
  4. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/account.py +1 -1
  5. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/base/session.py +4 -3
  6. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/client.py +67 -61
  7. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/errors.py +8 -14
  8. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/utils/__init__.py +2 -0
  9. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/utils/other.py +4 -0
  10. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/README.md +0 -0
  11. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/__init__.py +0 -0
  12. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/core/__init__.py +0 -0
  13. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/core/base.py +0 -0
  14. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/core/config.py +0 -0
  15. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/core/enum.py +0 -0
  16. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/core/serializer.py +0 -0
  17. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/_capsolver/fun_captcha.py +0 -0
  18. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/base/__init__.py +0 -0
  19. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/base/client.py +0 -0
  20. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/enums.py +0 -0
  21. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/models.py +0 -0
  22. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/utils/file.py +0 -0
  23. {tweepy_self-1.11.0 → tweepy_self-1.12.0}/twitter/utils/html.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tweepy-self
3
- Version: 1.11.0
3
+ Version: 1.12.0
4
4
  Summary: Twitter (selfbot) for Python!
5
5
  Home-page: https://github.com/alenkimov/tweepy-self
6
6
  Author: Alen
@@ -12,13 +12,13 @@ Classifier: Programming Language :: Python :: 3.12
12
12
  Requires-Dist: aiohttp (>=3.9,<4.0)
13
13
  Requires-Dist: beautifulsoup4 (>=4,<5)
14
14
  Requires-Dist: better-proxy (>=1.1,<2.0)
15
- Requires-Dist: curl_cffi (==0.6.2)
15
+ Requires-Dist: curl_cffi (==0.9.0b2)
16
16
  Requires-Dist: loguru (>=0.7,<0.8)
17
17
  Requires-Dist: lxml (>=5,<6)
18
18
  Requires-Dist: pydantic (>=2,<3)
19
19
  Requires-Dist: pyotp (>=2,<3)
20
20
  Requires-Dist: requests (>=2,<3)
21
- Requires-Dist: tenacity (>=8,<9)
21
+ Requires-Dist: tenacity (>=9,<10)
22
22
  Requires-Dist: yarl (>=1,<2)
23
23
  Project-URL: Repository, https://github.com/alenkimov/tweepy-self
24
24
  Project-URL: Source, https://github.com/alenkimov/tweepy-self
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tweepy-self"
3
- version = "1.11.0"
3
+ version = "1.12.0"
4
4
  description = "Twitter (selfbot) for Python!"
5
5
  authors = ["Alen <alen.kimov@gmail.com>"]
6
6
  readme = "README.md"
@@ -12,7 +12,7 @@ Source = "https://github.com/alenkimov/tweepy-self"
12
12
 
13
13
  [tool.poetry.dependencies]
14
14
  python = "^3.11"
15
- curl_cffi = "0.6.2"
15
+ curl_cffi = "0.9.0b2"
16
16
  better-proxy = "^1.1"
17
17
  beautifulsoup4 = "^4"
18
18
  pydantic = "^2"
@@ -20,7 +20,7 @@ lxml = "^5"
20
20
  pyotp = "^2"
21
21
  yarl = "^1"
22
22
  aiohttp = "^3.9"
23
- tenacity = "^8"
23
+ tenacity = "^9"
24
24
  requests = "^2"
25
25
  loguru = "^0.7"
26
26
 
@@ -30,11 +30,6 @@ __all__ = [
30
30
  ]
31
31
 
32
32
 
33
- import warnings
34
-
35
- # HACK: Ignore event loop warnings from curl_cffi
36
- warnings.filterwarnings("ignore", module="curl_cffi")
37
-
38
33
  from loguru import logger
39
34
 
40
35
  logger.disable("twitter")
@@ -50,7 +50,7 @@ class Account(User):
50
50
 
51
51
  def get_totp_code(self) -> str | None:
52
52
  if not self.totp_secret:
53
- raise ValueError("No key2fa")
53
+ raise ValueError("No totp_secret")
54
54
 
55
55
  return str(pyotp.TOTP(self.totp_secret).now())
56
56
 
@@ -14,13 +14,14 @@ class BaseAsyncSession(requests.AsyncSession):
14
14
  DEFAULT_HEADERS = {
15
15
  "accept": "*/*",
16
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"',
17
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
18
+ 'Priority': 'u=1, i',
19
+ "sec-ch-ua": '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
19
20
  "sec-ch-ua-platform": '"Windows"',
20
21
  "sec-ch-ua-mobile": "?0",
21
22
  "sec-fetch-dest": "empty",
22
23
  "sec-fetch-mode": "cors",
23
- "sec-fetch-site": "same-origin",
24
+ "sec-fetch-site": "ssame-site",
24
25
  "connection": "keep-alive",
25
26
  }
26
27
  DEFAULT_IMPERSONATE = requests.BrowserType.chrome120
@@ -31,27 +31,26 @@ from .errors import (
31
31
  from .base import BaseHTTPClient
32
32
  from .account import Account, AccountStatus
33
33
  from .models import User, Tweet, Media, Subtask
34
- from .utils import (
35
- parse_oauth_html,
36
- parse_unlock_html,
37
- tweets_data_from_instructions,
38
- )
34
+ from .utils import parse_oauth_html
35
+ from .utils import parse_unlock_html
36
+ from .utils import tweets_data_from_instructions
37
+ from .utils import encode_x_client_transaction_id
39
38
 
40
39
 
41
40
  class Client(BaseHTTPClient):
42
41
  _BEARER_TOKEN = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
43
42
  _DEFAULT_HEADERS = {
44
- "authority": "twitter.com",
45
- "origin": "https://twitter.com",
43
+ "authority": "x.com",
44
+ "origin": "https://x.com",
46
45
  "x-twitter-active-user": "yes",
47
46
  "x-twitter-client-language": "en",
48
47
  }
49
- _GRAPHQL_URL = "https://twitter.com/i/api/graphql"
48
+ _GRAPHQL_URL = "https://x.com/i/api/graphql"
50
49
  _ACTION_TO_QUERY_ID = {
51
50
  "CreateRetweet": "ojPdsZsimiJrUGLR1sjUtA",
52
51
  "FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
53
52
  "UnfavoriteTweet": "ZYKSe-w7KEslx3JhSIk5LA",
54
- "CreateTweet": "v0en1yVV-Ybeek8ClmXwYw",
53
+ "CreateTweet": "oB-5XsHNAbjvARJEc8CZFw",
55
54
  "TweetResultByRestId": "V3vfsYzNEyD9tsf4xoFRgw",
56
55
  "ModerateTweet": "p'jF:GVqCjTcZol0xcBJjw",
57
56
  "DeleteTweet": "VaenaVgh5q5ih7kvyVjgtg",
@@ -62,9 +61,9 @@ class Client(BaseHTTPClient):
62
61
  "Followers": "3yX7xr2hKjcZYnXt6cU6lQ",
63
62
  "UserByScreenName": "G3KGOASz96M-Qu0nwmGXNg",
64
63
  "UsersByRestIds": "itEhGywpgX9b3GJCzOtSrA",
65
- "Viewer": "W62NnYgkgziw9bwyoVht0g",
64
+ "Viewer": "-876iyxD1O_0X0BqeykjZA",
66
65
  }
67
- _CAPTCHA_URL = "https://twitter.com/account/access"
66
+ _CAPTCHA_URL = "https://x.com/account/access"
68
67
  _CAPTCHA_SITE_KEY = "0152B4EB-D2DC-460A-89A1-629838B529C9"
69
68
 
70
69
  @classmethod
@@ -103,16 +102,19 @@ class Client(BaseHTTPClient):
103
102
 
104
103
  async def _request(
105
104
  self,
106
- method,
107
- url,
105
+ method: str,
106
+ url: str | URL,
108
107
  *,
109
108
  auth: bool = True,
110
109
  bearer: bool = True,
111
110
  wait_on_rate_limit: bool = None,
112
111
  **kwargs,
113
112
  ) -> tuple[requests.Response, Any]:
114
- cookies = kwargs["cookies"] = kwargs.get("cookies") or {}
115
- headers = kwargs["headers"] = kwargs.get("headers") or {}
113
+ cookies = kwargs["cookies"] = kwargs.get("cookies", {})
114
+ headers = kwargs["headers"] = kwargs.get("headers", {})
115
+
116
+ url = URL(url)
117
+ headers["x-client-transaction-id"] = encode_x_client_transaction_id(url.path)
116
118
 
117
119
  if bearer:
118
120
  headers["authorization"] = f"Bearer {self._BEARER_TOKEN}"
@@ -141,7 +143,7 @@ class Client(BaseHTTPClient):
141
143
  # fmt: on
142
144
 
143
145
  try:
144
- response = await self._session.request(method, url, **kwargs)
146
+ response = await self._session.request(method, str(url), **kwargs)
145
147
  except requests.errors.RequestsError as exc:
146
148
  if exc.code == 35:
147
149
  msg = (
@@ -159,7 +161,7 @@ class Client(BaseHTTPClient):
159
161
  f"\nResponse data: {data}")
160
162
  # fmt: on
161
163
 
162
- if ct0 := self._session.cookies.get("ct0", domain=".twitter.com"):
164
+ if ct0 := self._session.cookies.get("ct0", domain=".x.com"):
163
165
  self.account.ct0 = ct0
164
166
 
165
167
  auth_token = self._session.cookies.get("auth_token")
@@ -179,12 +181,12 @@ class Client(BaseHTTPClient):
179
181
  if isinstance(data, dict) and "errors" in data:
180
182
  exc = HTTPException(response, data)
181
183
 
182
- if 141 in exc.api_codes or 37 in exc.api_codes:
184
+ if 141 in exc.error_codes or 37 in exc.error_codes:
183
185
  self.account.status = AccountStatus.SUSPENDED
184
186
  raise AccountSuspended(exc, self.account)
185
187
 
186
- if 326 in exc.api_codes:
187
- for error_data in exc.api_errors:
188
+ if 326 in exc.error_codes:
189
+ for error_data in exc.errors:
188
190
  if (
189
191
  error_data.get("code") == 326
190
192
  and error_data.get("bounce_location")
@@ -202,7 +204,7 @@ class Client(BaseHTTPClient):
202
204
  if response.status_code == 400:
203
205
  exc = BadRequest(response, data)
204
206
 
205
- if 399 in exc.api_codes:
207
+ if 399 in exc.error_codes:
206
208
  self.account.status = AccountStatus.NOT_FOUND
207
209
  raise AccountNotFound(exc, self.account)
208
210
 
@@ -211,7 +213,7 @@ class Client(BaseHTTPClient):
211
213
  if response.status_code == 401:
212
214
  exc = Unauthorized(response, data)
213
215
 
214
- if 32 in exc.api_codes:
216
+ if 32 in exc.error_codes:
215
217
  self.account.status = AccountStatus.BAD_TOKEN
216
218
  raise BadAccountToken(exc, self.account)
217
219
 
@@ -220,12 +222,12 @@ class Client(BaseHTTPClient):
220
222
  if response.status_code == 403:
221
223
  exc = Forbidden(response, data)
222
224
 
223
- if 64 in exc.api_codes:
225
+ if 64 in exc.error_codes:
224
226
  self.account.status = AccountStatus.SUSPENDED
225
227
  raise AccountSuspended(exc, self.account)
226
228
 
227
- if 326 in exc.api_codes:
228
- for error_data in exc.api_errors:
229
+ if 326 in exc.error_codes:
230
+ for error_data in exc.errors:
229
231
  if (
230
232
  error_data.get("code") == 326
231
233
  and error_data.get("bounce_location") == "/i/flow/consent_flow"
@@ -269,8 +271,8 @@ class Client(BaseHTTPClient):
269
271
 
270
272
  async def request(
271
273
  self,
272
- method,
273
- url,
274
+ method: str,
275
+ url: str | URL,
274
276
  *,
275
277
  auto_unlock: bool = True,
276
278
  auto_relogin: bool = None,
@@ -303,7 +305,7 @@ class Client(BaseHTTPClient):
303
305
  except Forbidden as exc:
304
306
  if (
305
307
  rerequest_on_bad_ct0
306
- and 353 in exc.api_codes
308
+ and 353 in exc.error_codes
307
309
  and "ct0" in exc.response.cookies
308
310
  ):
309
311
  return await self.request(
@@ -327,7 +329,7 @@ class Client(BaseHTTPClient):
327
329
  scope: str,
328
330
  response_type: str,
329
331
  ) -> str:
330
- url = "https://twitter.com/i/api/2/oauth2/authorize"
332
+ url = "https://x.com/i/api/2/oauth2/authorize"
331
333
  querystring = {
332
334
  "client_id": client_id,
333
335
  "code_challenge": code_challenge,
@@ -349,7 +351,7 @@ class Client(BaseHTTPClient):
349
351
  headers = {"content-type": "application/x-www-form-urlencoded"}
350
352
  await self.request(
351
353
  "POST",
352
- "https://twitter.com/i/api/2/oauth2/authorize",
354
+ "https://x.com/i/api/2/oauth2/authorize",
353
355
  headers=headers,
354
356
  data=data,
355
357
  )
@@ -393,7 +395,7 @@ class Client(BaseHTTPClient):
393
395
 
394
396
  :return: Response: html страница привязки приложения (аутентификации) старого типа.
395
397
  """
396
- url = "https://api.twitter.com/oauth/authenticate"
398
+ url = "https://api.x.com/oauth/authenticate"
397
399
  oauth_params["oauth_token"] = oauth_token
398
400
  response, _ = await self.request("GET", url, params=oauth_params)
399
401
 
@@ -411,7 +413,7 @@ class Client(BaseHTTPClient):
411
413
  authenticity_token: str,
412
414
  redirect_after_login_url: str,
413
415
  ) -> requests.Response:
414
- url = "https://api.twitter.com/oauth/authorize"
416
+ url = "https://api.x.com/oauth/authorize"
415
417
  params = {
416
418
  "redirect_after_login": redirect_after_login_url,
417
419
  "authenticity_token": authenticity_token,
@@ -441,7 +443,7 @@ class Client(BaseHTTPClient):
441
443
  return authenticity_token, redirect_url
442
444
 
443
445
  async def _update_account_username(self):
444
- url = "https://twitter.com/i/api/1.1/account/settings.json"
446
+ url = "https://api.x.com/1.1/account/settings.json"
445
447
  response, response_json = await self.request("POST", url)
446
448
  self.account.username = response_json["screen_name"]
447
449
 
@@ -556,7 +558,7 @@ class Client(BaseHTTPClient):
556
558
 
557
559
  :return: Media
558
560
  """
559
- url = "https://upload.twitter.com/1.1/media/upload.json"
561
+ url = "https://upload.x.com/1.1/media/upload.json"
560
562
  payload = {"media_data": base64.b64encode(image)}
561
563
  for attempt in range(attempts):
562
564
  try:
@@ -581,7 +583,7 @@ class Client(BaseHTTPClient):
581
583
  raise
582
584
 
583
585
  async def _follow_action(self, action: str, user_id: int | str) -> bool:
584
- url = f"https://twitter.com/i/api/1.1/friendships/{action}.json"
586
+ url = f"https://x.com/i/api/1.1/friendships/{action}.json"
585
587
  params = {
586
588
  "include_profile_interstitial_type": "1",
587
589
  "include_blocking": "1",
@@ -638,7 +640,7 @@ class Client(BaseHTTPClient):
638
640
  if (
639
641
  search_duplicate
640
642
  and 327
641
- in exc.api_codes # duplicate retweet (You have already retweeted this Tweet)
643
+ in exc.error_codes # duplicate retweet (You have already retweeted this Tweet)
642
644
  ):
643
645
  tweets = await self.request_tweets(self.account.id)
644
646
  duplicate_tweet = None
@@ -682,7 +684,7 @@ class Client(BaseHTTPClient):
682
684
  try:
683
685
  response_json = await self._interact_with_tweet("FavoriteTweet", tweet_id)
684
686
  except HTTPException as exc:
685
- if 139 in exc.api_codes:
687
+ if 139 in exc.error_codes:
686
688
  # Already liked
687
689
  return True
688
690
  else:
@@ -711,7 +713,7 @@ class Client(BaseHTTPClient):
711
713
  return is_deleted
712
714
 
713
715
  async def pin_tweet(self, tweet_id: str | int) -> bool:
714
- url = "https://api.twitter.com/1.1/account/pin_tweet.json"
716
+ url = "https://api.x.com/1.1/account/pin_tweet.json"
715
717
  data = {
716
718
  "tweet_mode": "extended",
717
719
  "id": str(tweet_id),
@@ -761,9 +763,12 @@ class Client(BaseHTTPClient):
761
763
  "longform_notetweets_consumption_enabled": True,
762
764
  "responsive_web_twitter_article_tweet_consumption_enabled": True,
763
765
  "tweet_awards_web_tipping_enabled": False,
766
+ "creator_subscriptions_quote_tweet_preview_enabled": False,
764
767
  "longform_notetweets_rich_text_read_enabled": True,
765
768
  "longform_notetweets_inline_media_enabled": True,
769
+ "articles_preview_enabled": True,
766
770
  "rweb_video_timestamps_enabled": True,
771
+ "rweb_tipjar_consumption_enabled": True,
767
772
  "responsive_web_graphql_exclude_directive_enabled": True,
768
773
  "verified_phone_label_enabled": False,
769
774
  "freedom_of_speech_not_reach_fetch_enabled": True,
@@ -803,7 +808,7 @@ class Client(BaseHTTPClient):
803
808
  except HTTPException as exc:
804
809
  if (
805
810
  search_duplicate
806
- and 187 in exc.api_codes # duplicate tweet (Status is a duplicate)
811
+ and 187 in exc.error_codes # duplicate tweet (Status is a duplicate)
807
812
  ):
808
813
  tweets = await self.request_tweets()
809
814
  duplicate_tweet = None
@@ -886,7 +891,7 @@ class Client(BaseHTTPClient):
886
891
  """
887
892
  :return: Raw vote information
888
893
  """
889
- url = "https://caps.twitter.com/v2/capi/passthrough/1"
894
+ url = "https://caps.x.com/v2/capi/passthrough/1"
890
895
  params = {
891
896
  "twitter:string:card_uri": f"card://{card_id}",
892
897
  "twitter:long:original_tweet_id": str(tweet_id),
@@ -1101,7 +1106,7 @@ class Client(BaseHTTPClient):
1101
1106
  """
1102
1107
  :return: Image URL
1103
1108
  """
1104
- url = f"https://api.twitter.com/1.1/account/update_profile_{type}.json"
1109
+ url = f"https://api.x.com/1.1/account/update_profile_{type}.json"
1105
1110
  params = {
1106
1111
  "media_id": str(media_id),
1107
1112
  "include_profile_interstitial_type": "1",
@@ -1136,7 +1141,7 @@ class Client(BaseHTTPClient):
1136
1141
  return await self._update_profile_image("banner", media_id)
1137
1142
 
1138
1143
  async def change_username(self, username: str) -> bool:
1139
- url = "https://twitter.com/i/api/1.1/account/settings.json"
1144
+ url = "https://x.com/i/api/1.1/account/settings.json"
1140
1145
  payload = {"screen_name": username}
1141
1146
  response, data = await self.request("POST", url, data=payload)
1142
1147
  new_username = data["screen_name"]
@@ -1151,7 +1156,7 @@ class Client(BaseHTTPClient):
1151
1156
  if not self.account.password:
1152
1157
  raise ValueError(f"Specify the current password before changing it")
1153
1158
 
1154
- url = "https://twitter.com/i/api/i/account/change_password.json"
1159
+ url = "https://x.com/i/api/i/account/change_password.json"
1155
1160
  payload = {
1156
1161
  "current_password": self.account.password,
1157
1162
  "password": password,
@@ -1175,7 +1180,7 @@ class Client(BaseHTTPClient):
1175
1180
  if name is None and description is None:
1176
1181
  raise ValueError("Specify at least one param")
1177
1182
 
1178
- url = "https://twitter.com/i/api/1.1/account/update_profile.json"
1183
+ url = "https://x.com/i/api/1.1/account/update_profile.json"
1179
1184
  # Создаем словарь data, включая в него только те ключи, для которых значения не равны None
1180
1185
  payload = {
1181
1186
  k: v
@@ -1200,9 +1205,9 @@ class Client(BaseHTTPClient):
1200
1205
  return updated
1201
1206
 
1202
1207
  async def establish_status(self):
1203
- url = "https://twitter.com/i/api/1.1/account/update_profile.json"
1208
+ url = "https://api.x.com/1.1/account/personalization/p13n_preferences.json"
1204
1209
  try:
1205
- await self.request("POST", url, auto_unlock=False, auto_relogin=False)
1210
+ await self.request("GET", url, auto_unlock=False, auto_relogin=False)
1206
1211
  self.account.status = AccountStatus.GOOD
1207
1212
  except BadAccount:
1208
1213
  pass
@@ -1215,7 +1220,7 @@ class Client(BaseHTTPClient):
1215
1220
  visibility: Literal["self", "mutualfollow"] = "self",
1216
1221
  year_visibility: Literal["self"] = "self",
1217
1222
  ) -> bool:
1218
- url = "https://twitter.com/i/api/1.1/account/update_profile.json"
1223
+ url = "https://x.com/i/api/1.1/account/update_profile.json"
1219
1224
  payload = {
1220
1225
  "birthdate_day": day,
1221
1226
  "birthdate_month": month,
@@ -1240,7 +1245,7 @@ class Client(BaseHTTPClient):
1240
1245
  """
1241
1246
  :return: Event data
1242
1247
  """
1243
- url = "https://api.twitter.com/1.1/direct_messages/events/new.json"
1248
+ url = "https://api.x.com/1.1/direct_messages/events/new.json"
1244
1249
  payload = {
1245
1250
  "event": {
1246
1251
  "type": "message_create",
@@ -1262,7 +1267,7 @@ class Client(BaseHTTPClient):
1262
1267
 
1263
1268
  :return: Event data
1264
1269
  """
1265
- url = f"https://api.twitter.com/2/dm_conversations/{conversation_id}/messages"
1270
+ url = f"https://api.x.com/2/dm_conversations/{conversation_id}/messages"
1266
1271
  payload = {"text": text}
1267
1272
  response, response_json = await self.request("POST", url, json=payload)
1268
1273
  event_data = response_json["event"]
@@ -1272,7 +1277,7 @@ class Client(BaseHTTPClient):
1272
1277
  """
1273
1278
  :return: Messages data
1274
1279
  """
1275
- url = "https://twitter.com/i/api/1.1/dm/inbox_initial_state.json"
1280
+ url = "https://x.com/i/api/1.1/dm/inbox_initial_state.json"
1276
1281
  params = {
1277
1282
  "nsfw_filtering_enabled": "false",
1278
1283
  "filter_low_quality": "false",
@@ -1414,7 +1419,7 @@ class Client(BaseHTTPClient):
1414
1419
  verification_string=token,
1415
1420
  )
1416
1421
 
1417
- if response.url == "https://twitter.com/?lang=en":
1422
+ if response.url == "https://x.com/?lang=en":
1418
1423
  break
1419
1424
 
1420
1425
  (
@@ -1444,7 +1449,7 @@ class Client(BaseHTTPClient):
1444
1449
  await self.establish_status()
1445
1450
 
1446
1451
  async def update_backup_code(self):
1447
- url = "https://api.twitter.com/1.1/account/backup_code.json"
1452
+ url = "https://api.x.com/1.1/account/backup_code.json"
1448
1453
  response, response_json = await self.request("GET", url)
1449
1454
  self.account.backup_code = response_json["codes"][0]
1450
1455
 
@@ -1452,7 +1457,7 @@ class Client(BaseHTTPClient):
1452
1457
  """
1453
1458
  :return: flow_token and subtasks
1454
1459
  """
1455
- url = "https://api.twitter.com/1.1/onboarding/task.json"
1460
+ url = "https://api.x.com/1.1/onboarding/task.json"
1456
1461
  response, data = await self.request("POST", url, **request_kwargs)
1457
1462
  subtasks = [
1458
1463
  Subtask.from_raw_data(subtask_data) for subtask_data in data["subtasks"]
@@ -1629,6 +1634,7 @@ class Client(BaseHTTPClient):
1629
1634
  async def _viewer(self):
1630
1635
  url, query_id = self._action_to_url("Viewer")
1631
1636
  features = {
1637
+ "rweb_tipjar_consumption_enabled": True,
1632
1638
  "responsive_web_graphql_exclude_directive_enabled": True,
1633
1639
  "verified_phone_label_enabled": False,
1634
1640
  "creator_subscriptions_tweet_preview_api_enabled": True,
@@ -1655,7 +1661,7 @@ class Client(BaseHTTPClient):
1655
1661
  """
1656
1662
  response, data = await self._request(
1657
1663
  "POST",
1658
- "https://api.twitter.com/1.1/guest/activate.json",
1664
+ "https://api.x.com/1.1/guest/activate.json",
1659
1665
  auth=False,
1660
1666
  )
1661
1667
  return data["guest_token"]
@@ -1697,7 +1703,7 @@ class Client(BaseHTTPClient):
1697
1703
  flow_token, subtasks = await self._login_acid(flow_token, self.account.email)
1698
1704
  # fmt: on
1699
1705
  except HTTPException as exc:
1700
- if 399 in exc.api_codes:
1706
+ if 399 in exc.error_codes:
1701
1707
  logger.warning(
1702
1708
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1703
1709
  f" Bad email!"
@@ -1721,7 +1727,7 @@ class Client(BaseHTTPClient):
1721
1727
  flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.get_totp_code())
1722
1728
  # fmt: on
1723
1729
  except HTTPException as exc:
1724
- if 399 in exc.api_codes:
1730
+ if 399 in exc.error_codes:
1725
1731
  logger.warning(
1726
1732
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1727
1733
  f" Bad TOTP secret!"
@@ -1737,7 +1743,7 @@ class Client(BaseHTTPClient):
1737
1743
  try:
1738
1744
  flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.backup_code)
1739
1745
  except HTTPException as exc:
1740
- if 399 in exc.api_codes:
1746
+ if 399 in exc.error_codes:
1741
1747
  logger.warning(
1742
1748
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1743
1749
  f" Bad backup code!"
@@ -1753,6 +1759,7 @@ class Client(BaseHTTPClient):
1753
1759
  else:
1754
1760
  raise
1755
1761
 
1762
+ await self._viewer()
1756
1763
  await self._complete_subtask(flow_token, [])
1757
1764
  return update_backup_code
1758
1765
 
@@ -1770,7 +1777,6 @@ class Client(BaseHTTPClient):
1770
1777
  raise ValueError("No password")
1771
1778
 
1772
1779
  update_backup_code = await self._login()
1773
- await self._viewer()
1774
1780
 
1775
1781
  if update_backup_code:
1776
1782
  await self.update_backup_code()
@@ -1794,7 +1800,7 @@ class Client(BaseHTTPClient):
1794
1800
  if not self.account.id:
1795
1801
  await self.update_account_info()
1796
1802
 
1797
- url = f"https://twitter.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
1803
+ url = f"https://x.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
1798
1804
  response, data = await self.request("GET", url)
1799
1805
  # fmt: off
1800
1806
  return "Totp" in [method_data["twoFactorType"] for method_data in data["methods"]]
@@ -1949,7 +1955,7 @@ class Client(BaseHTTPClient):
1949
1955
 
1950
1956
 
1951
1957
  class GQLClient:
1952
- _GRAPHQL_URL = "https://twitter.com/i/api/graphql"
1958
+ _GRAPHQL_URL = "https://x.com/i/api/graphql"
1953
1959
  _OPERATION_TO_QUERY_ID = {
1954
1960
  "CreateRetweet": "ojPdsZsimiJrUGLR1sjUtA",
1955
1961
  "FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
@@ -1965,7 +1971,7 @@ class GQLClient:
1965
1971
  "Followers": "3yX7xr2hKjcZYnXt6cU6lQ",
1966
1972
  "UserByScreenName": "G3KGOASz96M-Qu0nwmGXNg",
1967
1973
  "UsersByRestIds": "itEhGywpgX9b3GJCzOtSrA",
1968
- "Viewer": "W62NnYgkgziw9bwyoVht0g",
1974
+ "Viewer": "-876iyxD1O_0X0BqeykjZA",
1969
1975
  }
1970
1976
  _DEFAULT_VARIABLES = {
1971
1977
  "count": 1000,
@@ -41,10 +41,7 @@ def _http_exception_message(
41
41
  if detail:
42
42
  exception_message += f"\n(detail) {detail}"
43
43
  for error in api_errors:
44
- if "code" in error and "message" in error:
45
- exception_message += f"\n(code {error['code']}) {error['message']}"
46
- elif "message" in error:
47
- exception_message += f"\n{error['message']}"
44
+ exception_message += f"\n{error}"
48
45
  return exception_message
49
46
 
50
47
 
@@ -58,9 +55,8 @@ class HTTPException(TwitterException):
58
55
  custom_exception_message: str = None,
59
56
  ):
60
57
  self.response = response
61
- self.api_errors: list[dict] = []
62
- self.api_codes: list[int] = []
63
- self.api_messages: list[str] = []
58
+ self.errors: list[dict] = []
59
+ self.error_codes: list[int] = []
64
60
  self.detail: str | None = None
65
61
  self.html: str | None = None
66
62
 
@@ -81,17 +77,15 @@ class HTTPException(TwitterException):
81
77
  super().__init__(exception_message)
82
78
  return
83
79
 
84
- self.api_errors = data.get("errors", [data])
80
+ self.errors = data.get("errors", [data])
85
81
  self.detail = data.get("detail")
86
82
 
87
- for error in self.api_errors:
83
+ for error in self.errors:
88
84
  if "code" in error:
89
- self.api_codes.append(error["code"])
90
- if "message" in error:
91
- self.api_messages.append(error["message"])
85
+ self.error_codes.append(error["code"])
92
86
 
93
87
  exception_message = _http_exception_message(
94
- response, self.api_errors, self.detail, custom_exception_message
88
+ response, self.errors, self.detail, custom_exception_message
95
89
  )
96
90
  super().__init__(exception_message)
97
91
 
@@ -143,7 +137,7 @@ class BadAccount(TwitterException):
143
137
  self.account = account
144
138
  exception_message = _http_exception_message(
145
139
  http_exception.response,
146
- http_exception.api_errors,
140
+ http_exception.errors,
147
141
  http_exception.detail,
148
142
  custom_exception_message or "Bad Twitter account.",
149
143
  )
@@ -17,6 +17,7 @@ from .other import (
17
17
  to_datetime,
18
18
  hidden_value,
19
19
  tweets_data_from_instructions,
20
+ encode_x_client_transaction_id,
20
21
  )
21
22
 
22
23
 
@@ -35,4 +36,5 @@ __all__ = [
35
36
  "to_datetime",
36
37
  "hidden_value",
37
38
  "tweets_data_from_instructions",
39
+ "encode_x_client_transaction_id",
38
40
  ]
@@ -1,4 +1,8 @@
1
1
  from datetime import datetime
2
+ import base64
3
+
4
+ def encode_x_client_transaction_id(path: str) -> str:
5
+ return base64.b64encode(f"e:{path}".encode()).decode()
2
6
 
3
7
 
4
8
  def remove_at_sign(username: str) -> str:
File without changes