From 81764ea0f802677d0a4cec4025b721d601e2751f Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 5 Feb 2025 01:19:06 +0100 Subject: [PATCH] Update endpoint versions, switch tweet endpoint --- src/auth.nim | 2 +- src/consts.nim | 46 ++++++++++++++++++++++++++++++---------------- src/parser.nim | 39 ++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/auth.nim b/src/auth.nim index 66656d3..fe265c9 100644 --- a/src/auth.nim +++ b/src/auth.nim @@ -9,7 +9,7 @@ const dayInSeconds = 24 * 60 * 60 apiMaxReqs: Table[Api, int] = { Api.search: 50, - Api.tweetDetail: 150, + Api.tweetDetail: 500, Api.userTweets: 500, Api.userTweetsAndReplies: 500, Api.userMedia: 500, diff --git a/src/consts.nim b/src/consts.nim index 3abd1bc..7c67706 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -5,22 +5,20 @@ const consumerKey* = "3nVuSoBZnx6U4vzUxf5w" consumerSecret* = "Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys" - api = parseUri("https://api.twitter.com") - activate* = $(api / "1.1/guest/activate.json") + gql = parseUri("https://api.x.com") / "graphql" - graphql = api / "graphql" - graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" - graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" - graphUserTweets* = graphql / "3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2" - graphUserTweetsAndReplies* = graphql / "8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2" - graphUserMedia* = graphql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" - graphTweet* = graphql / "q94uRCEn65LZThakYcPT6g/TweetDetail" - graphTweetResult* = graphql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" - graphSearchTimeline* = graphql / "gkjsKepM6gl_HmFWoWKfgg/SearchTimeline" - graphListById* = graphql / "iTpgCtbdxrsJfyx0cFjHqg/ListByRestId" - graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug" - graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers" - graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" + graphUser* = gql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery" + graphUserById* = gql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery" + graphUserTweets* = gql / "JLApJKFY0MxGTzCoK6ps8Q/UserWithProfileTweetsQueryV2" + graphUserTweetsAndReplies* = gql / "Y86LQY7KMvxn5tu3hFTyPg/UserWithProfileTweetsAndRepliesQueryV2" + graphUserMedia* = gql / "PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2" + graphTweet* = gql / "Vorskcd2tZ-tc4Gx3zbk4Q/ConversationTimelineV2" + graphTweetResult* = gql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" + graphSearchTimeline* = gql / "KI9jCXUx3Ymt-hDKLOZb9Q/SearchTimeline" + graphListById* = gql / "oygmAig8kjn0pKsx_bUadQ/ListByRestId" + graphListBySlug* = gql / "88GTz-IPPWLn1EiU8XoNVg/ListBySlug" + graphListMembers* = gql / "kSmxeqEeelqdHSR7jMnb_w/ListMembers" + graphListTweets* = gql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline" gqlFeatures* = """{ "android_graphql_skip_api_media_color_palette": false, @@ -62,7 +60,23 @@ const "unified_cards_ad_metadata_container_dynamic_card_content_query_enabled": false, "verified_phone_label_enabled": false, "vibe_api_enabled": false, - "view_counts_everywhere_api_enabled": false + "view_counts_everywhere_api_enabled": false, + "premium_content_api_read_enabled": false, + "communities_web_enable_tweet_community_results_fetch": false, + "responsive_web_jetfuel_frame": false, + "responsive_web_grok_analyze_button_fetch_trends_enabled": false, + "responsive_web_grok_image_annotation_enabled": false, + "rweb_tipjar_consumption_enabled": false, + "profile_label_improvements_pcf_label_in_post_enabled": false, + "creator_subscriptions_quote_tweet_preview_enabled": false, + "c9s_tweet_anatomy_moderator_badge_enabled": false, + "responsive_web_grok_analyze_post_followups_enabled": false, + "rweb_video_timestamps_enabled": false, + "responsive_web_grok_share_attachment_enabled": false, + "articles_preview_enabled": false, + "immersive_video_status_linkable_timestamps": false, + "articles_api_enabled": false, + "responsive_web_grok_analysis_button_from_backend": false }""".replace(" ", "").replace("\n", "") tweetVariables* = """{ diff --git a/src/parser.nim b/src/parser.nim index b8812d2..4bdb1e2 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -352,18 +352,23 @@ proc parseGraphTweetResult*(js: JsonNode): Tweet = with tweet, js{"data", "tweet_result", "result"}: result = parseGraphTweet(tweet, false) -proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = +proc parseGraphConversation*(js: JsonNode; tweetId: string; v2=true): Conversation = result = Conversation(replies: Result[Chain](beginning: true)) - let instructions = ? js{"data", "threaded_conversation_with_injections_v2", "instructions"} + let + rootKey = if v2: "timeline_response" else: "threaded_conversation_with_injections_v2" + contentKey = if v2: "content" else: "itemContent" + resultKey = if v2: "tweetResult" else: "tweet_results" + + let instructions = ? js{"data", rootKey, "instructions"} if instructions.len == 0: return for e in instructions[0]{"entries"}: let entryId = e{"entryId"}.getStr if entryId.startsWith("tweet"): - with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: - let tweet = parseGraphTweet(tweetResult, true) + with tweetResult, e{"content", contentKey, resultKey, "result"}: + let tweet = parseGraphTweet(tweetResult, not v2) if not tweet.available: tweet.id = parseBiggestInt(entryId.getId()) @@ -372,26 +377,26 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = result.tweet = tweet else: result.before.content.add tweet - elif entryId.startsWith("tombstone"): - let id = entryId.getId() - let tweet = Tweet( - id: parseBiggestInt(id), - available: false, - text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone - ) - - if id == tweetId: - result.tweet = tweet - else: - result.before.content.add tweet elif entryId.startsWith("conversationthread"): let (thread, self) = parseGraphThread(e) if self: result.after = thread else: result.replies.content.add thread + elif entryId.startsWith("tombstone"): + let id = entryId.getId() + let tweet = Tweet( + id: parseBiggestInt(id), + available: false, + text: e{"content", contentKey, "tombstoneInfo", "richText"}.getTombstone + ) + + if id == tweetId: + result.tweet = tweet + else: + result.before.content.add tweet elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", "itemContent", "value"}.getStr + result.replies.bottom = e{"content", contentKey, "value"}.getStr proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0))