зеркало из
https://github.com/viginum-datalab/twscrape.git
synced 2025-10-29 13:06:13 +02:00
Update api.py
Этот коммит содержится в:
родитель
00a8e07b43
Коммит
1848cc996d
@ -20,6 +20,7 @@ OP_ListLatestTweetsTimeline = "HjsWc-nwwHKYwHenbHm-tw/ListLatestTweetsTimeline"
|
|||||||
OP_Likes = "9s8V6sUI8fZLDiN-REkAxA/Likes"
|
OP_Likes = "9s8V6sUI8fZLDiN-REkAxA/Likes"
|
||||||
OP_BlueVerifiedFollowers = "mg4dFO4kMIKt6tpqPMmFeg/BlueVerifiedFollowers"
|
OP_BlueVerifiedFollowers = "mg4dFO4kMIKt6tpqPMmFeg/BlueVerifiedFollowers"
|
||||||
OP_UserCreatorSubscriptions = "3IgWXBdSRADe5MkzziJV0A/UserCreatorSubscriptions"
|
OP_UserCreatorSubscriptions = "3IgWXBdSRADe5MkzziJV0A/UserCreatorSubscriptions"
|
||||||
|
OP_UserMedia = "C86A9b0nwmJW2lNz_-Ej4w/UserMedia"
|
||||||
|
|
||||||
|
|
||||||
GQL_URL = "https://twitter.com/i/api/graphql"
|
GQL_URL = "https://twitter.com/i/api/graphql"
|
||||||
@ -47,6 +48,28 @@ GQL_FEATURES = { # search values here (view source) https://twitter.com/
|
|||||||
"rweb_video_timestamps_enabled": True,
|
"rweb_video_timestamps_enabled": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GQL_FEATURES_MEDIA = {
|
||||||
|
"responsive_web_graphql_exclude_directive_enabled":True,
|
||||||
|
"verified_phone_label_enabled":False,
|
||||||
|
"creator_subscriptions_tweet_preview_api_enabled":True,
|
||||||
|
"responsive_web_graphql_timeline_navigation_enabled":True,
|
||||||
|
"responsive_web_graphql_skip_user_profile_image_extensions_enabled":False,
|
||||||
|
"c9s_tweet_anatomy_moderator_badge_enabled":True,
|
||||||
|
"tweetypie_unmention_optimization_enabled":True,
|
||||||
|
"responsive_web_edit_tweet_api_enabled":True,
|
||||||
|
"graphql_is_translatable_rweb_tweet_is_translatable_enabled":True,
|
||||||
|
"view_counts_everywhere_api_enabled":True,
|
||||||
|
"longform_notetweets_consumption_enabled":True,
|
||||||
|
"responsive_web_twitter_article_tweet_consumption_enabled":True,
|
||||||
|
"tweet_awards_web_tipping_enabled":False,
|
||||||
|
"freedom_of_speech_not_reach_fetch_enabled":True,
|
||||||
|
"standardized_nudges_misinfo":True,
|
||||||
|
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":True,
|
||||||
|
"rweb_video_timestamps_enabled":True,
|
||||||
|
"longform_notetweets_rich_text_read_enabled":True,
|
||||||
|
"longform_notetweets_inline_media_enabled":True,
|
||||||
|
"responsive_web_enhance_cards_enabled":False
|
||||||
|
}
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
# Note: kv is variables, ft is features from original GQL request
|
# Note: kv is variables, ft is features from original GQL request
|
||||||
@ -90,7 +113,10 @@ class API:
|
|||||||
self, op: str, kv: dict, ft: dict | None = None, limit=-1, cursor_type="Bottom"
|
self, op: str, kv: dict, ft: dict | None = None, limit=-1, cursor_type="Bottom"
|
||||||
):
|
):
|
||||||
queue, cur, cnt, active = op.split("/")[-1], None, 0, True
|
queue, cur, cnt, active = op.split("/")[-1], None, 0, True
|
||||||
kv, ft = {**kv}, {**GQL_FEATURES, **(ft or {})}
|
if queue == "UserMedia":
|
||||||
|
kv, ft = {**kv}, {**GQL_FEATURES_MEDIA, **(ft or {})}
|
||||||
|
else:
|
||||||
|
kv, ft = {**kv}, {**GQL_FEATURES, **(ft or {})}
|
||||||
|
|
||||||
async with QueueClient(self.pool, queue, self.debug, proxy=self.proxy) as client:
|
async with QueueClient(self.pool, queue, self.debug, proxy=self.proxy) as client:
|
||||||
while active:
|
while active:
|
||||||
@ -106,6 +132,13 @@ class API:
|
|||||||
|
|
||||||
obj = rep.json()
|
obj = rep.json()
|
||||||
els = get_by_path(obj, "entries") or []
|
els = get_by_path(obj, "entries") or []
|
||||||
|
|
||||||
|
if queue == "UserMedia" and els:
|
||||||
|
try:
|
||||||
|
els = els[0]["content"]["items"]
|
||||||
|
except:
|
||||||
|
els = get_by_path(obj, "moduleItems") or []
|
||||||
|
|
||||||
els = [x for x in els if not x["entryId"].startswith("cursor-")]
|
els = [x for x in els if not x["entryId"].startswith("cursor-")]
|
||||||
cur = self._get_cursor(obj, cursor_type)
|
cur = self._get_cursor(obj, cursor_type)
|
||||||
|
|
||||||
@ -327,6 +360,28 @@ class API:
|
|||||||
for x in parse_tweets(rep.json(), limit):
|
for x in parse_tweets(rep.json(), limit):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
#user_media
|
||||||
|
|
||||||
|
async def user_media_raw(self, uid: int, limit=-1, kv=None):
|
||||||
|
op = OP_UserMedia
|
||||||
|
kv = {
|
||||||
|
"userId": str(uid),
|
||||||
|
"count": 40,
|
||||||
|
"includePromotedContent": False,
|
||||||
|
"withClientEventToken":False,
|
||||||
|
"withBirdwatchNotes":False,
|
||||||
|
"withVoice": True,
|
||||||
|
"withV2Timeline": True,
|
||||||
|
**(kv or {}),
|
||||||
|
}
|
||||||
|
async for x in self._gql_items(op, kv, limit=limit):
|
||||||
|
yield x
|
||||||
|
|
||||||
|
async def user_media(self, uid: int, limit=-1, kv=None):
|
||||||
|
async for rep in self.user_media_raw(uid, limit=limit, kv=kv):
|
||||||
|
for x in parse_tweets(rep.json(), limit):
|
||||||
|
yield x
|
||||||
|
|
||||||
# user_tweets_and_replies
|
# user_tweets_and_replies
|
||||||
|
|
||||||
async def user_tweets_and_replies_raw(self, uid: int, limit=-1, kv=None):
|
async def user_tweets_and_replies_raw(self, uid: int, limit=-1, kv=None):
|
||||||
|
|||||||
Загрузка…
x
Ссылка в новой задаче
Block a user