tweepy-self 1.11.1__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.1 → tweepy_self-1.12.0}/PKG-INFO +3 -3
  2. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/pyproject.toml +3 -3
  3. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/__init__.py +0 -5
  4. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/client.py +31 -28
  5. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/errors.py +8 -14
  6. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/utils/__init__.py +2 -0
  7. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/utils/other.py +4 -0
  8. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/README.md +0 -0
  9. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/__init__.py +0 -0
  10. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/core/__init__.py +0 -0
  11. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/core/base.py +0 -0
  12. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/core/config.py +0 -0
  13. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/core/enum.py +0 -0
  14. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/core/serializer.py +0 -0
  15. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/_capsolver/fun_captcha.py +0 -0
  16. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/account.py +0 -0
  17. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/base/__init__.py +0 -0
  18. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/base/client.py +0 -0
  19. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/base/session.py +0 -0
  20. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/enums.py +0 -0
  21. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/models.py +0 -0
  22. {tweepy_self-1.11.1 → tweepy_self-1.12.0}/twitter/utils/file.py +0 -0
  23. {tweepy_self-1.11.1 → 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.1
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.1"
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")
@@ -31,11 +31,10 @@ 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):
@@ -103,8 +102,8 @@ 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,
@@ -113,6 +112,10 @@ class Client(BaseHTTPClient):
113
112
  ) -> tuple[requests.Response, Any]:
114
113
  cookies = kwargs["cookies"] = kwargs.get("cookies", {})
115
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)
118
+
116
119
  if bearer:
117
120
  headers["authorization"] = f"Bearer {self._BEARER_TOKEN}"
118
121
 
@@ -140,7 +143,7 @@ class Client(BaseHTTPClient):
140
143
  # fmt: on
141
144
 
142
145
  try:
143
- response = await self._session.request(method, url, **kwargs)
146
+ response = await self._session.request(method, str(url), **kwargs)
144
147
  except requests.errors.RequestsError as exc:
145
148
  if exc.code == 35:
146
149
  msg = (
@@ -178,12 +181,12 @@ class Client(BaseHTTPClient):
178
181
  if isinstance(data, dict) and "errors" in data:
179
182
  exc = HTTPException(response, data)
180
183
 
181
- 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:
182
185
  self.account.status = AccountStatus.SUSPENDED
183
186
  raise AccountSuspended(exc, self.account)
184
187
 
185
- if 326 in exc.api_codes:
186
- for error_data in exc.api_errors:
188
+ if 326 in exc.error_codes:
189
+ for error_data in exc.errors:
187
190
  if (
188
191
  error_data.get("code") == 326
189
192
  and error_data.get("bounce_location")
@@ -201,7 +204,7 @@ class Client(BaseHTTPClient):
201
204
  if response.status_code == 400:
202
205
  exc = BadRequest(response, data)
203
206
 
204
- if 399 in exc.api_codes:
207
+ if 399 in exc.error_codes:
205
208
  self.account.status = AccountStatus.NOT_FOUND
206
209
  raise AccountNotFound(exc, self.account)
207
210
 
@@ -210,7 +213,7 @@ class Client(BaseHTTPClient):
210
213
  if response.status_code == 401:
211
214
  exc = Unauthorized(response, data)
212
215
 
213
- if 32 in exc.api_codes:
216
+ if 32 in exc.error_codes:
214
217
  self.account.status = AccountStatus.BAD_TOKEN
215
218
  raise BadAccountToken(exc, self.account)
216
219
 
@@ -219,12 +222,12 @@ class Client(BaseHTTPClient):
219
222
  if response.status_code == 403:
220
223
  exc = Forbidden(response, data)
221
224
 
222
- if 64 in exc.api_codes:
225
+ if 64 in exc.error_codes:
223
226
  self.account.status = AccountStatus.SUSPENDED
224
227
  raise AccountSuspended(exc, self.account)
225
228
 
226
- if 326 in exc.api_codes:
227
- for error_data in exc.api_errors:
229
+ if 326 in exc.error_codes:
230
+ for error_data in exc.errors:
228
231
  if (
229
232
  error_data.get("code") == 326
230
233
  and error_data.get("bounce_location") == "/i/flow/consent_flow"
@@ -268,8 +271,8 @@ class Client(BaseHTTPClient):
268
271
 
269
272
  async def request(
270
273
  self,
271
- method,
272
- url,
274
+ method: str,
275
+ url: str | URL,
273
276
  *,
274
277
  auto_unlock: bool = True,
275
278
  auto_relogin: bool = None,
@@ -302,7 +305,7 @@ class Client(BaseHTTPClient):
302
305
  except Forbidden as exc:
303
306
  if (
304
307
  rerequest_on_bad_ct0
305
- and 353 in exc.api_codes
308
+ and 353 in exc.error_codes
306
309
  and "ct0" in exc.response.cookies
307
310
  ):
308
311
  return await self.request(
@@ -440,7 +443,7 @@ class Client(BaseHTTPClient):
440
443
  return authenticity_token, redirect_url
441
444
 
442
445
  async def _update_account_username(self):
443
- url = "https://x.com/i/api/1.1/account/settings.json"
446
+ url = "https://api.x.com/1.1/account/settings.json"
444
447
  response, response_json = await self.request("POST", url)
445
448
  self.account.username = response_json["screen_name"]
446
449
 
@@ -637,7 +640,7 @@ class Client(BaseHTTPClient):
637
640
  if (
638
641
  search_duplicate
639
642
  and 327
640
- 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)
641
644
  ):
642
645
  tweets = await self.request_tweets(self.account.id)
643
646
  duplicate_tweet = None
@@ -681,7 +684,7 @@ class Client(BaseHTTPClient):
681
684
  try:
682
685
  response_json = await self._interact_with_tweet("FavoriteTweet", tweet_id)
683
686
  except HTTPException as exc:
684
- if 139 in exc.api_codes:
687
+ if 139 in exc.error_codes:
685
688
  # Already liked
686
689
  return True
687
690
  else:
@@ -805,7 +808,7 @@ class Client(BaseHTTPClient):
805
808
  except HTTPException as exc:
806
809
  if (
807
810
  search_duplicate
808
- 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)
809
812
  ):
810
813
  tweets = await self.request_tweets()
811
814
  duplicate_tweet = None
@@ -1202,9 +1205,9 @@ class Client(BaseHTTPClient):
1202
1205
  return updated
1203
1206
 
1204
1207
  async def establish_status(self):
1205
- url = "https://x.com/i/api/1.1/account/update_profile.json"
1208
+ url = "https://api.x.com/1.1/account/personalization/p13n_preferences.json"
1206
1209
  try:
1207
- await self.request("POST", url, auto_unlock=False, auto_relogin=False)
1210
+ await self.request("GET", url, auto_unlock=False, auto_relogin=False)
1208
1211
  self.account.status = AccountStatus.GOOD
1209
1212
  except BadAccount:
1210
1213
  pass
@@ -1700,7 +1703,7 @@ class Client(BaseHTTPClient):
1700
1703
  flow_token, subtasks = await self._login_acid(flow_token, self.account.email)
1701
1704
  # fmt: on
1702
1705
  except HTTPException as exc:
1703
- if 399 in exc.api_codes:
1706
+ if 399 in exc.error_codes:
1704
1707
  logger.warning(
1705
1708
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1706
1709
  f" Bad email!"
@@ -1724,7 +1727,7 @@ class Client(BaseHTTPClient):
1724
1727
  flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.get_totp_code())
1725
1728
  # fmt: on
1726
1729
  except HTTPException as exc:
1727
- if 399 in exc.api_codes:
1730
+ if 399 in exc.error_codes:
1728
1731
  logger.warning(
1729
1732
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1730
1733
  f" Bad TOTP secret!"
@@ -1740,7 +1743,7 @@ class Client(BaseHTTPClient):
1740
1743
  try:
1741
1744
  flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.backup_code)
1742
1745
  except HTTPException as exc:
1743
- if 399 in exc.api_codes:
1746
+ if 399 in exc.error_codes:
1744
1747
  logger.warning(
1745
1748
  f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
1746
1749
  f" Bad backup code!"
@@ -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