From e3e00965c8999df9dd51935aae223362f5ba8758 Mon Sep 17 00:00:00 2001 From: abhinav7x94 <204053250+abhinav7x94@users.noreply.github.com> Date: Sun, 16 Aug 2026 04:14:49 +0530 Subject: [PATCH] fix(extension): import visibility-wrapped X bookmarks --- apps/browser-extension/utils/twitter-utils.ts | 13 ++++- .../utils/twitter-visibility-result.test.ts | 55 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 apps/browser-extension/utils/twitter-visibility-result.test.ts diff --git a/apps/browser-extension/utils/twitter-utils.ts b/apps/browser-extension/utils/twitter-utils.ts index c3295db4..741fe174 100644 --- a/apps/browser-extension/utils/twitter-utils.ts +++ b/apps/browser-extension/utils/twitter-utils.ts @@ -46,6 +46,11 @@ interface TwitterAPITweet { } } +interface TwitterAPIVisibilityResult { + __typename: "TweetWithVisibilityResults" + tweet?: TwitterAPITweet +} + interface MediaEntity { type: string media_url_https: string @@ -270,9 +275,13 @@ export function transformTweetData( return null } - const tweet = tweetData as TwitterAPITweet + const visibilityResult = tweetData as TwitterAPIVisibilityResult + const tweet = + visibilityResult.__typename === "TweetWithVisibilityResults" + ? visibilityResult.tweet + : (tweetData as TwitterAPITweet) - if (!tweet.legacy) { + if (!tweet?.legacy) { return null } diff --git a/apps/browser-extension/utils/twitter-visibility-result.test.ts b/apps/browser-extension/utils/twitter-visibility-result.test.ts new file mode 100644 index 00000000..6c08184a --- /dev/null +++ b/apps/browser-extension/utils/twitter-visibility-result.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "bun:test" +import { getAllTweets, type TwitterAPIResponse } from "./twitter-utils" + +const apiTweet = (id: string) => ({ + __typename: "Tweet", + legacy: { + favorite_count: 0, + created_at: "Wed Oct 10 20:19:24 +0000 2018", + id_str: id, + full_text: `Tweet ${id}`, + }, +}) + +const timelineEntry = (id: string, result: unknown) => ({ + entryId: `tweet-${id}`, + sortIndex: id, + content: { itemContent: { tweet_results: { result } } }, +}) + +describe("getAllTweets", () => { + it("extracts visibility-wrapped results and skips unavailable tweets", () => { + const response: TwitterAPIResponse = { + data: { + bookmark_timeline_v2: { + timeline: { + instructions: [ + { + type: "TimelineAddEntries", + entries: [ + timelineEntry("100", apiTweet("100")), + timelineEntry("200", { + __typename: "TweetWithVisibilityResults", + limitedActionResults: {}, + tweet: apiTweet("200"), + }), + timelineEntry("300", { + __typename: "TweetTombstone", + }), + timelineEntry("400", { + __typename: "TweetWithVisibilityResults", + }), + ], + }, + ], + }, + }, + }, + } + + expect(getAllTweets(response).map((tweet) => tweet.id_str)).toEqual([ + "100", + "200", + ]) + }) +})