[twitter] update API endpoint query hashes (#7382 #7386)

and associated 'variables', 'features', and 'fieldToggles' parameters
This commit is contained in:
Mike Fährmann
2025-04-19 13:09:12 +02:00
parent 10f2e08bd1
commit 2798fb8a80

View File

@@ -1139,54 +1139,76 @@ class TwitterAPI():
"collab_control,vibe", "collab_control,vibe",
} }
self.features = { self.features = {
"hidden_profile_likes_enabled": True,
"hidden_profile_subscriptions_enabled": True, "hidden_profile_subscriptions_enabled": True,
"profile_label_improvements_pcf_label_in_post_enabled": True,
"rweb_tipjar_consumption_enabled": True,
"responsive_web_graphql_exclude_directive_enabled": True, "responsive_web_graphql_exclude_directive_enabled": True,
"verified_phone_label_enabled": False, "verified_phone_label_enabled": False,
"highlights_tweets_tab_ui_enabled": True, "highlights_tweets_tab_ui_enabled": True,
"responsive_web_twitter_article_notes_tab_enabled": True, "responsive_web_twitter_article_notes_tab_enabled": True,
"subscriptions_feature_can_gift_premium": True,
"creator_subscriptions_tweet_preview_api_enabled": True, "creator_subscriptions_tweet_preview_api_enabled": True,
"responsive_web_graphql_" "responsive_web_graphql_"
"skip_user_profile_image_extensions_enabled": False, "skip_user_profile_image_extensions_enabled": False,
"responsive_web_graphql_timeline_navigation_enabled": True, "responsive_web_graphql_"
"timeline_navigation_enabled": True,
} }
self.features_pagination = { self.features_pagination = {
"rweb_video_screen_enabled": False,
"profile_label_improvements_pcf_label_in_post_enabled": True,
"rweb_tipjar_consumption_enabled": True,
"responsive_web_graphql_exclude_directive_enabled": True, "responsive_web_graphql_exclude_directive_enabled": True,
"verified_phone_label_enabled": False, "verified_phone_label_enabled": False,
"creator_subscriptions_tweet_preview_api_enabled": True, "creator_subscriptions_tweet_preview_api_enabled": True,
"responsive_web_graphql_timeline_navigation_enabled": True, "responsive_web_graphql_"
"responsive_web_graphql_skip_user_profile_" "timeline_navigation_enabled": True,
"image_extensions_enabled": False, "responsive_web_graphql_"
"skip_user_profile_image_extensions_enabled": False,
"premium_content_api_read_enabled": False,
"communities_web_enable_tweet_community_results_fetch": True,
"c9s_tweet_anatomy_moderator_badge_enabled": True, "c9s_tweet_anatomy_moderator_badge_enabled": True,
"tweetypie_unmention_optimization_enabled": True, "responsive_web_grok_analyze_button_fetch_trends_enabled": False,
"responsive_web_grok_analyze_post_followups_enabled": True,
"responsive_web_jetfuel_frame": False,
"responsive_web_grok_share_attachment_enabled": True,
"articles_preview_enabled": True,
"responsive_web_edit_tweet_api_enabled": True, "responsive_web_edit_tweet_api_enabled": True,
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": True, "graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
"view_counts_everywhere_api_enabled": True, "view_counts_everywhere_api_enabled": True,
"longform_notetweets_consumption_enabled": True, "longform_notetweets_consumption_enabled": True,
"responsive_web_twitter_article_tweet_consumption_enabled": True, "responsive_web_twitter_article_tweet_consumption_enabled": True,
"tweet_awards_web_tipping_enabled": False, "tweet_awards_web_tipping_enabled": False,
"responsive_web_grok_show_grok_translated_post": False,
"responsive_web_grok_analysis_button_from_backend": True,
"creator_subscriptions_quote_tweet_preview_enabled": False,
"freedom_of_speech_not_reach_fetch_enabled": True, "freedom_of_speech_not_reach_fetch_enabled": True,
"standardized_nudges_misinfo": True, "standardized_nudges_misinfo": True,
"tweet_with_visibility_results_prefer_gql_" "tweet_with_visibility_results_"
"limited_actions_policy_enabled": True, "prefer_gql_limited_actions_policy_enabled": True,
"rweb_video_timestamps_enabled": True,
"longform_notetweets_rich_text_read_enabled": True, "longform_notetweets_rich_text_read_enabled": True,
"longform_notetweets_inline_media_enabled": True, "longform_notetweets_inline_media_enabled": True,
"responsive_web_media_download_video_enabled": True, "responsive_web_grok_image_annotation_enabled": True,
"responsive_web_enhance_cards_enabled": False, "responsive_web_enhance_cards_enabled": False,
} }
def tweet_result_by_rest_id(self, tweet_id): def tweet_result_by_rest_id(self, tweet_id):
endpoint = "/graphql/MWY3AO9_I3rcP_L2A4FR4A/TweetResultByRestId" endpoint = "/graphql/Vg2Akr5FzUmF0sTplA5k6g/TweetResultByRestId"
variables = { variables = {
"tweetId": tweet_id, "tweetId": tweet_id,
"withCommunity": False, "withCommunity": False,
"includePromotedContent": False, "includePromotedContent": False,
"withVoice": False, "withVoice": False,
} }
field_toggles = {
"withArticleRichContentState": True,
"withArticlePlainText": False,
"withGrokAnalyze": False,
"withDisallowedReplyControls": False,
}
params = { params = {
"variables": self._json_dumps(variables), "variables" : self._json_dumps(variables),
"features" : self._json_dumps(self.features_pagination), "features" : self._json_dumps(self.features_pagination),
"fieldToggles": self._json_dumps(field_toggles),
} }
tweet = self._call(endpoint, params)["data"]["tweetResult"]["result"] tweet = self._call(endpoint, params)["data"]["tweetResult"]["result"]
if "tweet" in tweet: if "tweet" in tweet:
@@ -1203,47 +1225,61 @@ class TwitterAPI():
return tweet return tweet
def tweet_detail(self, tweet_id): def tweet_detail(self, tweet_id):
endpoint = "/graphql/B9_KmbkLhXt6jRwGjJrweg/TweetDetail" endpoint = "/graphql/b9Yw90FMr_zUb8DvA8r2ug/TweetDetail"
variables = { variables = {
"focalTweetId": tweet_id, "focalTweetId": tweet_id,
"referrer": "profile", "referrer": "profile",
"with_rux_injections": False, "with_rux_injections": False,
# "rankingMode": "Relevance",
"includePromotedContent": False, "includePromotedContent": False,
"withCommunity": True, "withCommunity": True,
"withQuickPromoteEligibilityTweetFields": True, "withQuickPromoteEligibilityTweetFields": False,
"withBirdwatchNotes": True, "withBirdwatchNotes": True,
"withVoice": True, "withVoice": True,
"withV2Timeline": True, }
field_toggles = {
"withArticleRichContentState": True,
"withArticlePlainText": False,
"withGrokAnalyze": False,
"withDisallowedReplyControls": False,
} }
return self._pagination_tweets( return self._pagination_tweets(
endpoint, variables, ("threaded_conversation_with_injections_v2",)) endpoint, variables,
("threaded_conversation_with_injections_v2",),
field_toggles=field_toggles)
def user_tweets(self, screen_name): def user_tweets(self, screen_name):
endpoint = "/graphql/5ICa5d9-AitXZrIA3H-4MQ/UserTweets" endpoint = "/graphql/M3Hpkrb8pjWkEuGdLeXMOA/UserTweets"
variables = { variables = {
"userId": self._user_id_by_screen_name(screen_name), "userId": self._user_id_by_screen_name(screen_name),
"count": 100, "count": 100,
"includePromotedContent": False, "includePromotedContent": False,
"withQuickPromoteEligibilityTweetFields": True, "withQuickPromoteEligibilityTweetFields": False,
"withVoice": True, "withVoice": True,
"withV2Timeline": True,
} }
return self._pagination_tweets(endpoint, variables) field_toggles = {
"withArticlePlainText": False,
}
return self._pagination_tweets(
endpoint, variables, field_toggles=field_toggles)
def user_tweets_and_replies(self, screen_name): def user_tweets_and_replies(self, screen_name):
endpoint = "/graphql/UtLStR_BnYUGD7Q453UXQg/UserTweetsAndReplies" endpoint = "/graphql/pz0IHaV_t7T4HJavqqqcIA/UserTweetsAndReplies"
variables = { variables = {
"userId": self._user_id_by_screen_name(screen_name), "userId": self._user_id_by_screen_name(screen_name),
"count": 100, "count": 100,
"includePromotedContent": False, "includePromotedContent": False,
"withCommunity": True, "withCommunity": True,
"withVoice": True, "withVoice": True,
"withV2Timeline": True,
} }
return self._pagination_tweets(endpoint, variables) field_toggles = {
"withArticlePlainText": False,
}
return self._pagination_tweets(
endpoint, variables, field_toggles=field_toggles)
def user_media(self, screen_name): def user_media(self, screen_name):
endpoint = "/graphql/tO4LMUYAZbR4T0SqQ85aAw/UserMedia" endpoint = "/graphql/8B9DqlaGvYyOvTCzzZWtNA/UserMedia"
variables = { variables = {
"userId": self._user_id_by_screen_name(screen_name), "userId": self._user_id_by_screen_name(screen_name),
"count": 100, "count": 100,
@@ -1251,12 +1287,15 @@ class TwitterAPI():
"withClientEventToken": False, "withClientEventToken": False,
"withBirdwatchNotes": False, "withBirdwatchNotes": False,
"withVoice": True, "withVoice": True,
"withV2Timeline": True,
} }
return self._pagination_tweets(endpoint, variables) field_toggles = {
"withArticlePlainText": False,
}
return self._pagination_tweets(
endpoint, variables, field_toggles=field_toggles)
def user_likes(self, screen_name): def user_likes(self, screen_name):
endpoint = "/graphql/9s8V6sUI8fZLDiN-REkAxA/Likes" endpoint = "/graphql/uxjTlmrTI61zreSIV1urbw/Likes"
variables = { variables = {
"userId": self._user_id_by_screen_name(screen_name), "userId": self._user_id_by_screen_name(screen_name),
"count": 100, "count": 100,
@@ -1264,24 +1303,24 @@ class TwitterAPI():
"withClientEventToken": False, "withClientEventToken": False,
"withBirdwatchNotes": False, "withBirdwatchNotes": False,
"withVoice": True, "withVoice": True,
"withV2Timeline": True,
} }
return self._pagination_tweets(endpoint, variables) field_toggles = {
"withArticlePlainText": False,
}
return self._pagination_tweets(
endpoint, variables, field_toggles=field_toggles)
def user_bookmarks(self): def user_bookmarks(self):
endpoint = "/graphql/cQxQgX8MJYjWwC0dxpyfYg/Bookmarks" endpoint = "/graphql/ztCdjqsvvdL0dE8R5ME0hQ/Bookmarks"
variables = { variables = {
"count": 100, "count": 100,
"includePromotedContent": False, "includePromotedContent": False,
} }
features = self.features_pagination.copy()
features["graphql_timeline_v2_bookmark_timeline"] = True
return self._pagination_tweets( return self._pagination_tweets(
endpoint, variables, ("bookmark_timeline_v2", "timeline"), False, endpoint, variables, ("bookmark_timeline_v2", "timeline"), False)
features=features)
def list_latest_tweets_timeline(self, list_id): def list_latest_tweets_timeline(self, list_id):
endpoint = "/graphql/HjsWc-nwwHKYwHenbHm-tw/ListLatestTweetsTimeline" endpoint = "/graphql/LSefrrxhpeX8HITbKfWz9g/ListLatestTweetsTimeline"
variables = { variables = {
"listId": list_id, "listId": list_id,
"count": 100, "count": 100,
@@ -1289,21 +1328,20 @@ class TwitterAPI():
return self._pagination_tweets( return self._pagination_tweets(
endpoint, variables, ("list", "tweets_timeline", "timeline")) endpoint, variables, ("list", "tweets_timeline", "timeline"))
def search_timeline(self, query): def search_timeline(self, query, product="Latest"):
endpoint = "/graphql/fZK7JipRHWtiZsTodhsTfQ/SearchTimeline" endpoint = "/graphql/fL2MBiqXPk5pSrOS5ACLdA/SearchTimeline"
variables = { variables = {
"rawQuery": query, "rawQuery": query,
"count": 100, "count": 100,
"querySource": "", "querySource": "typed_query",
"product": "Latest", "product": product,
} }
return self._pagination_tweets( return self._pagination_tweets(
endpoint, variables, endpoint, variables,
("search_by_raw_query", "search_timeline", "timeline")) ("search_by_raw_query", "search_timeline", "timeline"))
def community_tweets_timeline(self, community_id): def community_tweets_timeline(self, community_id):
endpoint = "/graphql/7B2AdxSuC-Er8qUr3Plm_w/CommunityTweetsTimeline" endpoint = "/graphql/awszcpgwaIeqqNfmzjxUow/CommunityTweetsTimeline"
variables = { variables = {
"communityId": community_id, "communityId": community_id,
"count": 100, "count": 100,
@@ -1317,7 +1355,7 @@ class TwitterAPI():
"timeline")) "timeline"))
def community_media_timeline(self, community_id): def community_media_timeline(self, community_id):
endpoint = "/graphql/qAGUldfcIoMv5KyAyVLYog/CommunityMediaTimeline" endpoint = "/graphql/HfMuDHto2j3NKUeiLjKWHA/CommunityMediaTimeline"
variables = { variables = {
"communityId": community_id, "communityId": community_id,
"count": 100, "count": 100,
@@ -1329,7 +1367,7 @@ class TwitterAPI():
"timeline")) "timeline"))
def communities_main_page_timeline(self, screen_name): def communities_main_page_timeline(self, screen_name):
endpoint = ("/graphql/GtOhw2mstITBepTRppL6Uw" endpoint = ("/graphql/NbdrKPY_h_nlvZUg7oqH5Q"
"/CommunitiesMainPageTimeline") "/CommunitiesMainPageTimeline")
variables = { variables = {
"count": 100, "count": 100,
@@ -1356,17 +1394,25 @@ class TwitterAPI():
["twitter_objects"]["live_events"][event_id]) ["twitter_objects"]["live_events"][event_id])
def list_members(self, list_id): def list_members(self, list_id):
endpoint = "/graphql/BQp2IEYkgxuSxqbTAr1e1g/ListMembers" endpoint = "/graphql/v97svwb-qcBmzv6QruDuNg/ListMembers"
variables = { variables = {
"listId": list_id, "listId": list_id,
"count": 100, "count": 100,
"withSafetyModeUserFields": True,
} }
return self._pagination_users( return self._pagination_users(
endpoint, variables, ("list", "members_timeline", "timeline")) endpoint, variables, ("list", "members_timeline", "timeline"))
def user_followers(self, screen_name):
endpoint = "/graphql/jqZ0_HJBA6mnu18iTZYm9w/Followers"
variables = {
"userId": self._user_id_by_screen_name(screen_name),
"count": 100,
"includePromotedContent": False,
}
return self._pagination_users(endpoint, variables)
def user_following(self, screen_name): def user_following(self, screen_name):
endpoint = "/graphql/PAnE9toEjRfE-4tozRcsfw/Following" endpoint = "/graphql/4QHbs4wmzgtU91f-t96_Eg/Following"
variables = { variables = {
"userId": self._user_id_by_screen_name(screen_name), "userId": self._user_id_by_screen_name(screen_name),
"count": 100, "count": 100,
@@ -1376,12 +1422,11 @@ class TwitterAPI():
@memcache(keyarg=1) @memcache(keyarg=1)
def user_by_rest_id(self, rest_id): def user_by_rest_id(self, rest_id):
endpoint = "/graphql/tD8zKvQzwY3kdx5yz6YmOw/UserByRestId" endpoint = "/graphql/5vdJ5sWkbSRDiiNZvwc2Yg/UserByRestId"
features = self.features features = self.features
params = { params = {
"variables": self._json_dumps({ "variables": self._json_dumps({
"userId": rest_id, "userId": rest_id,
"withSafetyModeUserFields": True,
}), }),
"features": self._json_dumps(features), "features": self._json_dumps(features),
} }
@@ -1389,7 +1434,7 @@ class TwitterAPI():
@memcache(keyarg=1) @memcache(keyarg=1)
def user_by_screen_name(self, screen_name): def user_by_screen_name(self, screen_name):
endpoint = "/graphql/k5XapwcSikNsEsILW5FvgA/UserByScreenName" endpoint = "/graphql/32pL5BWe9WKeSK1MoPvFQQ/UserByScreenName"
features = self.features.copy() features = self.features.copy()
features["subscriptions_verification_info_" features["subscriptions_verification_info_"
"is_identity_verified_enabled"] = True "is_identity_verified_enabled"] = True
@@ -1398,9 +1443,11 @@ class TwitterAPI():
params = { params = {
"variables": self._json_dumps({ "variables": self._json_dumps({
"screen_name": screen_name, "screen_name": screen_name,
"withSafetyModeUserFields": True,
}), }),
"features": self._json_dumps(features), "features": self._json_dumps(features),
"fieldToggles": self._json_dumps({
"withAuxiliaryUserLabels": True,
}),
} }
return self._call(endpoint, params)["data"]["user"]["result"] return self._call(endpoint, params)["data"]["user"]["result"]
@@ -1620,7 +1667,8 @@ class TwitterAPI():
params["cursor"] = extr._update_cursor(cursor) params["cursor"] = extr._update_cursor(cursor)
def _pagination_tweets(self, endpoint, variables, def _pagination_tweets(self, endpoint, variables,
path=None, stop_tweets=True, features=None): path=None, stop_tweets=True,
features=None, field_toggles=None):
extr = self.extractor extr = self.extractor
original_retweets = (extr.retweets == "original") original_retweets = (extr.retweets == "original")
pinned_tweet = extr.pinned pinned_tweet = extr.pinned
@@ -1633,6 +1681,8 @@ class TwitterAPI():
features = self.features_pagination features = self.features_pagination
if features: if features:
params["features"] = self._json_dumps(features) params["features"] = self._json_dumps(features)
if field_toggles:
params["fieldToggles"] = self._json_dumps(field_toggles)
while True: while True:
params["variables"] = self._json_dumps(variables) params["variables"] = self._json_dumps(variables)
@@ -1640,7 +1690,7 @@ class TwitterAPI():
try: try:
if path is None: if path is None:
instructions = (data["user"]["result"]["timeline_v2"] instructions = (data["user"]["result"]["timeline"]
["timeline"]["instructions"]) ["timeline"]["instructions"])
else: else:
instructions = data instructions = data