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.
- {tweepy_self-1.6.3.dist-info → tweepy_self-1.10.0b1.dist-info}/METADATA +16 -9
- tweepy_self-1.10.0b1.dist-info/RECORD +23 -0
- twitter/__init__.py +15 -5
- twitter/_capsolver/__init__.py +0 -0
- twitter/_capsolver/core/__init__.py +0 -0
- twitter/_capsolver/core/base.py +227 -0
- twitter/_capsolver/core/config.py +36 -0
- twitter/_capsolver/core/enum.py +66 -0
- twitter/_capsolver/core/serializer.py +85 -0
- twitter/_capsolver/fun_captcha.py +260 -0
- twitter/account.py +17 -13
- twitter/base/__init__.py +2 -2
- twitter/base/client.py +4 -4
- twitter/client.py +388 -222
- twitter/errors.py +14 -7
- twitter/models.py +126 -50
- twitter/utils/__init__.py +2 -0
- twitter/utils/other.py +13 -0
- tweepy_self-1.6.3.dist-info/RECORD +0 -16
- {tweepy_self-1.6.3.dist-info → tweepy_self-1.10.0b1.dist-info}/WHEEL +0 -0
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
|
-
|
68
|
-
|
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 =
|
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
|
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
|
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
|
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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
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
|
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
|
-
|
40
|
-
id:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
retweeted:
|
49
|
-
|
50
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
return f"({self.id}
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
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,,
|
File without changes
|