tweepy-self 1.6.3__py3-none-any.whl → 1.10.0b1__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
@@ -61,12 +61,17 @@ class HTTPException(TwitterException):
61
61
  self.api_codes: list[int] = []
62
62
  self.api_messages: list[str] = []
63
63
  self.detail: str | None = None
64
+ self.html: str | None = None
64
65
 
65
66
  # Если ответ — строка, то это html
66
67
  if isinstance(data, str):
67
- exception_message = (
68
- f"(response status: {response.status_code}) HTML Response:\n{data}"
69
- )
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}"
70
75
  if response.status_code == 429:
71
76
  exception_message = (
72
77
  f"(response status: {response.status_code}) Rate limit exceeded."
@@ -75,7 +80,7 @@ class HTTPException(TwitterException):
75
80
  super().__init__(exception_message)
76
81
  return
77
82
 
78
- self.api_errors = data.get("errors", [])
83
+ self.api_errors = data.get("errors", [data])
79
84
  self.detail = data.get("detail")
80
85
 
81
86
  for error in self.api_errors:
@@ -146,7 +151,9 @@ class BadAccount(TwitterException):
146
151
 
147
152
  class BadToken(BadAccount):
148
153
  def __init__(self, http_exception: "HTTPException", account: Account):
149
- exception_message = "Bad Twitter account's auth_token."
154
+ exception_message = (
155
+ "Bad Twitter account's auth_token. Relogin to get new token."
156
+ )
150
157
  super().__init__(http_exception, account, exception_message)
151
158
 
152
159
 
@@ -154,14 +161,14 @@ class Locked(BadAccount):
154
161
  def __init__(self, http_exception: "HTTPException", account: Account):
155
162
  exception_message = (
156
163
  f"Twitter account is locked."
157
- f" Set CapSolver API key (capsolver_api_key) to autounlock."
164
+ f" Set CapSolver API key (capsolver_api_key) to auto-unlock."
158
165
  )
159
166
  super().__init__(http_exception, account, exception_message)
160
167
 
161
168
 
162
169
  class ConsentLocked(BadAccount):
163
170
  def __init__(self, http_exception: "HTTPException", account: Account):
164
- exception_message = f"Twitter account is consent locked. Relogin to unlock."
171
+ exception_message = f"Twitter account is locked."
165
172
  super().__init__(http_exception, account, exception_message)
166
173
 
167
174
 
twitter/models.py CHANGED
@@ -1,26 +1,53 @@
1
- from datetime import datetime
1
+ from typing import Optional
2
+ from datetime import datetime, timedelta
2
3
 
3
- from pydantic import BaseModel
4
+ from pydantic import BaseModel, Field, field_validator
4
5
 
5
- from .utils import to_datetime
6
+ from .utils import to_datetime, tweet_url
6
7
 
7
8
 
8
- class UserData(BaseModel):
9
- id: int
10
- username: str
11
- name: str
12
- created_at: datetime
13
- description: str
14
- location: str
15
- followers_count: int
16
- friends_count: int
17
- raw_data: dict
9
+ class Image(BaseModel):
10
+ type: str = Field(..., alias="image_type")
11
+ width: int = Field(..., alias="w")
12
+ height: int = Field(..., alias="h")
13
+
14
+
15
+ class Media(BaseModel):
16
+ id: int = Field(..., alias="media_id")
17
+ image: Image
18
+ size: int
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)
18
25
 
19
26
  def __str__(self):
20
- return f"({self.id}) @{self.username}"
27
+ return str(self.id)
28
+
29
+
30
+ class User(BaseModel):
31
+ # fmt: off
32
+ id: int | None = None
33
+ username: str | None = None
34
+ name: str | None = None # 50
35
+ created_at: datetime | None = None
36
+ description: str | None = None # 160
37
+ location: str | None = None # 30
38
+ followers_count: int | None = None
39
+ friends_count: int | None = None
40
+ raw_data: dict | None = None
41
+ # fmt: on
42
+
43
+ def __str__(self):
44
+ return str(self.id)
45
+
46
+ def __repr__(self):
47
+ return f"{self.__class__.__name__}(id={self.id}, username={self.username})"
21
48
 
22
49
  @classmethod
23
- def from_raw_user_data(cls, data: dict):
50
+ def from_raw_data(cls, data: dict):
24
51
  legacy = data["legacy"]
25
52
  keys = ("name", "description", "location", "followers_count", "friends_count")
26
53
  values = {key: legacy[key] for key in keys}
@@ -36,44 +63,93 @@ class UserData(BaseModel):
36
63
 
37
64
 
38
65
  class Tweet(BaseModel):
39
- user_id: int
40
- id: int
41
- created_at: datetime
42
- full_text: str
43
- lang: str
44
- favorite_count: int
45
- quote_count: int
46
- reply_count: int
47
- retweet_count: int
48
- retweeted: bool
49
- raw_data: dict
50
- url: str | None = None
66
+ # fmt: off
67
+ id: int
68
+ text: str
69
+ language: str
70
+ created_at: datetime
71
+
72
+ conversation_id: int
73
+
74
+ quoted: bool
75
+ retweeted: bool
76
+ bookmarked: bool
77
+ favorited: bool
78
+
79
+ quote_count: int
80
+ retweet_count: int
81
+ bookmark_count: int
82
+ favorite_count: int
83
+ reply_count: int
84
+
85
+ quoted_tweet: Optional["Tweet"] = None
86
+ retweeted_tweet: Optional["Tweet"] = None
87
+
88
+ user: User
89
+ url: str
90
+
91
+ raw_data: dict
92
+
93
+ # TODO hashtags
94
+ # TODO media
95
+ # TODO symbols
96
+ # TODO timestamps
97
+ # TODO urls
98
+ # TODO user_mentions
99
+ # TODO views
100
+ # fmt: on
51
101
 
52
102
  def __str__(self):
53
- short_text = (
54
- f"{self.full_text[:32]}..." if len(self.full_text) > 16 else self.full_text
55
- )
56
- return f"({self.id}) {short_text}"
103
+ return str(self.id)
104
+
105
+ def __repr__(self):
106
+ return f"{self.__class__.__name__}(id={self.id}, user_id={self.user.id})"
107
+
108
+ @property
109
+ def short_text(self) -> str:
110
+ return f"{self.text[:32]}..." if len(self.text) > 16 else self.text
57
111
 
58
112
  @classmethod
59
113
  def from_raw_data(cls, data: dict):
60
- legacy = data["legacy"]
61
- keys = (
62
- "full_text",
63
- "lang",
64
- "favorite_count",
65
- "quote_count",
66
- "reply_count",
67
- "retweet_count",
68
- "retweeted",
69
- )
70
- values = {key: legacy[key] for key in keys}
71
- values.update(
72
- {
73
- "user_id": int(legacy["user_id_str"]),
74
- "id": int(legacy["id_str"]),
75
- "created_at": to_datetime(legacy["created_at"]),
76
- "raw_data": data,
77
- }
78
- )
114
+ legacy_data = data["legacy"]
115
+
116
+ user_data = data["core"]["user_results"]["result"]
117
+ user = User.from_raw_data(user_data)
118
+
119
+ id = int(legacy_data["id_str"])
120
+ url = tweet_url(user.username, id)
121
+
122
+ retweeted_tweet = None
123
+ if "retweeted_status_result" in legacy_data:
124
+ retweeted_tweet_data = legacy_data["retweeted_status_result"]["result"]
125
+ retweeted_tweet = cls.from_raw_data(retweeted_tweet_data)
126
+
127
+ quoted_tweet = None
128
+ if "quoted_status_result" in data:
129
+ quoted_tweet_data = data["quoted_status_result"]["result"]
130
+ quoted_tweet = cls.from_raw_data(quoted_tweet_data)
131
+
132
+ values = {
133
+ "id": id,
134
+ "text": legacy_data["full_text"],
135
+ "language": legacy_data["lang"],
136
+ "created_at": to_datetime(legacy_data["created_at"]),
137
+ "conversation_id": int(legacy_data["conversation_id_str"]),
138
+ "quoted": legacy_data["is_quote_status"],
139
+ "retweeted": legacy_data["retweeted"],
140
+ "bookmarked": legacy_data["bookmarked"],
141
+ "favorited": legacy_data["favorited"],
142
+ "quote_count": legacy_data["quote_count"],
143
+ "retweet_count": legacy_data["retweet_count"],
144
+ "bookmark_count": legacy_data["bookmark_count"],
145
+ "favorite_count": legacy_data["favorite_count"],
146
+ "reply_count": legacy_data["reply_count"],
147
+ "user": user.model_dump(),
148
+ "quoted_tweet": quoted_tweet.model_dump() if quoted_tweet else None,
149
+ "retweeted_tweet": (
150
+ retweeted_tweet.model_dump() if retweeted_tweet else None
151
+ ),
152
+ "url": url,
153
+ "raw_data": data,
154
+ }
79
155
  return cls(**values)
twitter/utils/__init__.py CHANGED
@@ -16,6 +16,7 @@ from .other import (
16
16
  tweet_url,
17
17
  to_datetime,
18
18
  hidden_value,
19
+ tweets_data_from_instructions,
19
20
  )
20
21
 
21
22
 
@@ -33,4 +34,5 @@ __all__ = [
33
34
  "tweet_url",
34
35
  "to_datetime",
35
36
  "hidden_value",
37
+ "tweets_data_from_instructions",
36
38
  ]
twitter/utils/other.py CHANGED
@@ -14,6 +14,19 @@ def tweet_url(username: str, tweet_id: int) -> str:
14
14
  return f"https://x.com/{username}/status/{tweet_id}"
15
15
 
16
16
 
17
+ def tweets_data_from_instructions(instructions: dict) -> list[dict]:
18
+ tweets = []
19
+ for instruction in instructions:
20
+ if instruction["type"] == "TimelineAddEntries":
21
+ for entry in instruction["entries"]:
22
+ if entry["entryId"].startswith("tweet-"):
23
+ tweet_data = entry["content"]["itemContent"]["tweet_results"][
24
+ "result"
25
+ ]
26
+ tweets.append(tweet_data)
27
+ return tweets
28
+
29
+
17
30
  def to_datetime(twitter_datetime: str):
18
31
  return datetime.strptime(twitter_datetime, "%a %b %d %H:%M:%S +0000 %Y")
19
32
 
@@ -1,16 +0,0 @@
1
- twitter/__init__.py,sha256=hdrsdbH_qFhx6ro1ct79qF9SpkgFhxgbYUw9A4RVuec,684
2
- twitter/account.py,sha256=tHzBdc34pEJI2SRbjLcmKtwAzjhzUFsa4vIQqtXAc1s,3015
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=JFPS-9Qae1iY3NfNcywxvWWmRDijaU_Rjs3WaQ00iFA,2071
6
- twitter/client.py,sha256=TXcIr-HnuqEtYOAiS5fmvY2P7KumlLE0lOAytMq89N8,61346
7
- twitter/enums.py,sha256=-OH6Ibxarq5qt4E2AhkProVawcEyIf5YG_h_G5xiV9Y,270
8
- twitter/errors.py,sha256=PsvGP3hps1HNWdKbUPhEkuDqLUmmqzhpAkRtVrJr5jc,5007
9
- twitter/models.py,sha256=ttSNuhY1knTdI-Yty54OCJF5YzARc8IaA11zKNHV9p0,2063
10
- twitter/utils/__init__.py,sha256=pyhQXwTdp0HFwV_UNF4dTyklLD9RtaefA16SrQXeNlg,589
11
- twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
12
- twitter/utils/html.py,sha256=hVtIRFI2yRAdWEaShFNBG-_ZWxd16og8i8OVDnFy5Hc,1971
13
- twitter/utils/other.py,sha256=UnUxS3uDR4eggbNN16xiw96VC_MNt9tOgnBlNWvRBoY,559
14
- tweepy_self-1.6.3.dist-info/METADATA,sha256=Nl46Hi1yQY6s47FPLnT0bMjhWTYR7gM18SJsZaDxhJQ,9304
15
- tweepy_self-1.6.3.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
- tweepy_self-1.6.3.dist-info/RECORD,,