diff --git a/src/api.nim b/src/api.nim index bc5db05..0a79071 100644 --- a/src/api.nim +++ b/src/api.nim @@ -100,7 +100,7 @@ proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} = let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" variables = tweetVariables % [id, cursor] - params = {"variables": variables, "features": gqlFeatures} + params = {"variables": variables, "features": gqlFeatures, "fieldToggles": tweetFieldToggles} js = await fetch(graphTweet ? params, Api.tweetDetail) result = parseGraphConversation(js, id) diff --git a/src/apiutils.nim b/src/apiutils.nim index c5fe79b..b92b9d6 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -116,7 +116,8 @@ template fetchImpl(result, additional_headers, fetchBody) {.dirty.} = #release(token, used=true) if resp.status == $Http400: - raise newException(InternalError, $url) + let errText = "body: '" & result & "' url: " & $url + raise newException(InternalError, errText) except InternalError as e: raise e except BadClientError as e: diff --git a/src/consts.nim b/src/consts.nim index 8eb2057..68ade47 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -20,7 +20,7 @@ const graphUserTweets* = graphql / "3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2" graphUserTweetsAndReplies* = graphql / "8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2" graphUserMedia* = graphql / "dexO_2tohK86JDudXXG3Yw/UserMedia" - graphTweet* = graphql / "q94uRCEn65LZThakYcPT6g/TweetDetail" + graphTweet* = graphql / "y90SwUGBZ3yz0yNUmCHgTw/TweetDetail" graphTweetResult* = graphql / "sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery" graphSearchTimeline* = graphql / "gkjsKepM6gl_HmFWoWKfgg/SearchTimeline" graphListById* = graphql / "iTpgCtbdxrsJfyx0cFjHqg/ListByRestId" @@ -95,30 +95,36 @@ 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, + "responsive_web_grok_analyze_button_fetch_trends_enabled": false, + "responsive_web_grok_analysis_button_from_backend": false, + "responsive_web_grok_analyze_post_followups_enabled": false, + "responsive_web_jetfuel_frame": false, + "profile_label_improvements_pcf_label_in_post_enabled": true, + "responsive_web_grok_image_annotation_enabled": false, + "responsive_web_grok_share_attachment_enabled": false, + "rweb_video_screen_enabled": false }""".replace(" ", "").replace("\n", "") tweetVariables* = """{ "focalTweetId": "$1", $2 - "includeHasBirdwatchNotes": false, + "with_rux_injections": false, + "rankingMode": "Relevance", "includePromotedContent": false, - "withBirdwatchNotes": false, - "withVoice": false, - "withV2Timeline": true + "withCommunity": true, + "withQuickPromoteEligibilityTweetFields": false, + "withBirdwatchNotes": true, + "withVoice": true }""".replace(" ", "").replace("\n", "") -# oldUserTweetsVariables* = """{ -# "userId": "$1", $2 -# "count": 20, -# "includePromotedContent": false, -# "withDownvotePerspective": false, -# "withReactionsMetadata": false, -# "withReactionsPerspective": false, -# "withVoice": false, -# "withV2Timeline": true -# } -# """.replace(" ", "").replace("\n", "") + tweetFieldToggles* = """{ + "withArticleRichContentState": false, + "withArticlePlainText": true, + "withGrokAnalyze": false, + "withDisallowedReplyControls": false +}""".replace(" ", "").replace("\n", "") userTweetsVariables* = """{ "rest_id": "$1", @@ -151,4 +157,4 @@ const $2 "count": 20, "includePromotedContent": false -}""".replace(" ", "").replace("\n", "") \ No newline at end of file +}""".replace(" ", "").replace("\n", "") diff --git a/src/parser.nim b/src/parser.nim index e306388..360b20a 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -505,39 +505,41 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation = 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) + for i in instructions: + if i{"type"}.getStr == "TimelineAddEntries": + for e in i{"entries"}: + let entryId = e{"entryId"}.getStr + if entryId.startsWith("tweet"): + with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: + let tweet = parseGraphTweet(tweetResult, true) - if not tweet.available: - tweet.id = parseBiggestInt(entryId.getId()) + if not tweet.available: + tweet.id = parseBiggestInt(entryId.getId()) - if $tweet.id == tweetId: - 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 $tweet.id == tweetId: + 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") or entryId.startswith("reply-mixer-conversation"): - let (thread, self) = parseGraphThread(e) - if self: - result.after = thread - else: - result.replies.content.add thread - elif entryId.startsWith("cursor-bottom"): - result.replies.bottom = e{"content", "itemContent", "value"}.getStr + if id == tweetId: + result.tweet = tweet + else: + result.before.content.add tweet + elif entryId.startsWith("conversationthread") or entryId.startswith("reply-mixer-conversation"): + let (thread, self) = parseGraphThread(e) + if self: + result.after = thread + else: + result.replies.content.add thread + elif entryId.startsWith("cursor-bottom"): + result.replies.bottom = e{"content", "value"}.getStr proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile = result = Profile(tweets: Timeline(beginning: after.len == 0))