From aeacf4d2ef72d0bbf0183367b764887e679d8bee Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Dec 2024 15:19:39 -0800 Subject: [PATCH 01/25] Start adding archiveBookmarks setting --- src/database.ts | 18 ++++++++++++++++++ .../src/views/x/XWizardArchiveOptionsPage.vue | 9 +++++++++ .../src/views/x/XWizardBuildOptionsPage.vue | 9 +++++++++ .../src/views/x/XWizardImportOrBuildPage.vue | 18 +++++++++--------- src/shared_types.ts | 2 ++ 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/database.ts b/src/database.ts index 8a609a44..bd70d9dc 100644 --- a/src/database.ts +++ b/src/database.ts @@ -157,6 +157,14 @@ export const runMainMigrations = () => { `ALTER TABLE xAccount ADD COLUMN archiveMyData BOOLEAN DEFAULT 0;`, ] }, + // Add archiveBookmarks, deleteBookmarks to xAccount + { + name: "add archiveBookmarks, deleteBookmarks to xAccount", + sql: [ + `ALTER TABLE xAccount ADD COLUMN archiveBookmarks BOOLEAN DEFAULT 1;`, + `ALTER TABLE xAccount ADD COLUMN deleteBookmarks BOOLEAN DEFAULT 0;`, + ] + }, ]); } @@ -196,6 +204,7 @@ interface XAccountRow { archiveTweets: boolean; archiveTweetsHTML: boolean; archiveLikes: boolean; + archiveBookmarks: boolean; archiveDMs: boolean; deleteTweets: boolean; deleteTweetsDaysOldEnabled: boolean; @@ -208,6 +217,7 @@ interface XAccountRow { deleteRetweetsDaysOldEnabled: boolean; deleteRetweetsDaysOld: number; deleteLikes: boolean; + deleteBookmarks: number; deleteDMs: boolean; unfollowEveryone: boolean; followingCount: number; @@ -390,6 +400,7 @@ export const getXAccount = (id: number): XAccount | null => { archiveTweets: !!row.archiveTweets, archiveTweetsHTML: !!row.archiveTweetsHTML, archiveLikes: !!row.archiveLikes, + archiveBookmarks: !!row.archiveBookmarks, archiveDMs: !!row.archiveDMs, deleteTweets: !!row.deleteTweets, deleteTweetsDaysOldEnabled: !!row.deleteTweetsDaysOldEnabled, @@ -402,6 +413,7 @@ export const getXAccount = (id: number): XAccount | null => { deleteRetweetsDaysOldEnabled: !!row.deleteRetweetsDaysOldEnabled, deleteRetweetsDaysOld: row.deleteRetweetsDaysOld, deleteLikes: !!row.deleteLikes, + deleteBookmarks: !!row.deleteBookmarks, deleteDMs: !!row.deleteDMs, unfollowEveryone: !!row.unfollowEveryone, followingCount: row.followingCount, @@ -430,6 +442,7 @@ export const getXAccounts = (): XAccount[] => { archiveTweets: !!row.archiveTweets, archiveTweetsHTML: !!row.archiveTweetsHTML, archiveLikes: !!row.archiveLikes, + archiveBookmarks: !!row.archiveBookmarks, archiveDMs: !!row.archiveDMs, deleteTweets: !!row.deleteTweets, deleteTweetsDaysOldEnabled: !!row.deleteTweetsDaysOldEnabled, @@ -442,6 +455,7 @@ export const getXAccounts = (): XAccount[] => { deleteRetweetsDaysOldEnabled: !!row.deleteRetweetsDaysOldEnabled, deleteRetweetsDaysOld: row.deleteRetweetsDaysOld, deleteLikes: !!row.deleteLikes, + deleteBookmarks: !!row.deleteBookmarks, deleteDMs: !!row.deleteDMs, unfollowEveryone: !!row.unfollowEveryone, followingCount: row.followingCount, @@ -478,6 +492,7 @@ export const saveXAccount = (account: XAccount) => { archiveTweets = ?, archiveTweetsHTML = ?, archiveLikes = ?, + archiveBookmarks = ?, archiveDMs = ?, deleteTweets = ?, deleteTweetsDaysOld = ?, @@ -490,6 +505,7 @@ export const saveXAccount = (account: XAccount) => { deleteRetweetsDaysOldEnabled = ?, deleteRetweetsDaysOld = ?, deleteLikes = ?, + deleteBookmarks = ?, deleteDMs = ?, unfollowEveryone = ?, followingCount = ?, @@ -507,6 +523,7 @@ export const saveXAccount = (account: XAccount) => { account.archiveTweets ? 1 : 0, account.archiveTweetsHTML ? 1 : 0, account.archiveLikes ? 1 : 0, + account.archiveBookmarks ? 1 : 0, account.archiveDMs ? 1 : 0, account.deleteTweets ? 1 : 0, account.deleteTweetsDaysOld, @@ -519,6 +536,7 @@ export const saveXAccount = (account: XAccount) => { account.deleteRetweetsDaysOldEnabled ? 1 : 0, account.deleteRetweetsDaysOld, account.deleteLikes ? 1 : 0, + account.deleteBookmarks ? 1 : 0, account.deleteDMs ? 1 : 0, account.unfollowEveryone ? 1 : 0, account.followingCount, diff --git a/src/renderer/src/views/x/XWizardArchiveOptionsPage.vue b/src/renderer/src/views/x/XWizardArchiveOptionsPage.vue index 756f7b68..fdcb3c47 100644 --- a/src/renderer/src/views/x/XWizardArchiveOptionsPage.vue +++ b/src/renderer/src/views/x/XWizardArchiveOptionsPage.vue @@ -28,6 +28,7 @@ const backClicked = async () => { // Settings const archiveTweetsHTML = ref(false); +const archiveBookmarks = ref(false); const archiveDMs = ref(false); const databaseStats = ref(emptyXDatabaseStats()); @@ -37,6 +38,7 @@ const loadSettings = async () => { const account = await window.electron.database.getAccount(props.model.account?.id); if (account && account.xAccount) { archiveTweetsHTML.value = account.xAccount.archiveTweetsHTML; + archiveBookmarks.value = account.xAccount.archiveBookmarks; archiveDMs.value = account.xAccount.archiveDMs; } }; @@ -54,6 +56,7 @@ const saveSettings = async () => { account.xAccount.archiveMyData = true; account.xAccount.archiveTweetsHTML = archiveTweetsHTML.value; + account.xAccount.archiveBookmarks = archiveBookmarks.value; account.xAccount.archiveDMs = archiveDMs.value; await window.electron.database.saveAccount(JSON.stringify(account)); emit('updateAccount'); @@ -105,6 +108,12 @@ onMounted(async () => { +
+
+ + +
+
diff --git a/src/renderer/src/views/x/XWizardBuildOptionsPage.vue b/src/renderer/src/views/x/XWizardBuildOptionsPage.vue index 73b7c600..0c4729b5 100644 --- a/src/renderer/src/views/x/XWizardBuildOptionsPage.vue +++ b/src/renderer/src/views/x/XWizardBuildOptionsPage.vue @@ -36,6 +36,7 @@ const backClicked = async () => { const archiveTweets = ref(false); const archiveTweetsHTML = ref(false); const archiveLikes = ref(false); +const archiveBookmarks = ref(false); const archiveDMs = ref(false); const loadSettings = async () => { @@ -45,6 +46,7 @@ const loadSettings = async () => { archiveTweets.value = account.xAccount.archiveTweets; archiveTweetsHTML.value = account.xAccount.archiveTweetsHTML; archiveLikes.value = account.xAccount.archiveLikes; + archiveBookmarks.value = account.xAccount.archiveBookmarks; archiveDMs.value = account.xAccount.archiveDMs; } }; @@ -64,6 +66,7 @@ const saveSettings = async () => { account.xAccount.archiveTweets = archiveTweets.value; account.xAccount.archiveTweetsHTML = archiveTweetsHTML.value; account.xAccount.archiveLikes = archiveLikes.value; + account.xAccount.archiveBookmarks = archiveBookmarks.value; account.xAccount.archiveDMs = archiveDMs.value; await window.electron.database.saveAccount(JSON.stringify(account)); emit('updateAccount'); @@ -122,6 +125,12 @@ onMounted(async () => {
+
+
+ + +
+
diff --git a/src/renderer/src/views/x/XWizardImportOrBuildPage.vue b/src/renderer/src/views/x/XWizardImportOrBuildPage.vue index 21a70413..df2c5740 100644 --- a/src/renderer/src/views/x/XWizardImportOrBuildPage.vue +++ b/src/renderer/src/views/x/XWizardImportOrBuildPage.vue @@ -125,13 +125,13 @@ onMounted(() => {
- Cyd can save an HTML version of each tweet for you, and also save a more detailed backup - of your direct messages than is available in the official X archive. + Cyd can save an HTML version of each tweet, and detailed backup of your direct messages, + and your bookmarks.
@@ -181,13 +181,13 @@ onMounted(() => {
- Cyd can save an HTML version of each tweet for you, and also save a more detailed backup - of your direct messages than is available in the official X archive. + Cyd can save an HTML version of each tweet, and detailed backup of your direct messages, + and your bookmarks.
@@ -237,13 +237,13 @@ onMounted(() => {
- Cyd can save an HTML version of each tweet for you, and also save a more detailed backup - of your direct messages than is available in the official X archive. + Cyd can save an HTML version of each tweet, and detailed backup of your direct messages, + and your bookmarks.
diff --git a/src/shared_types.ts b/src/shared_types.ts index c3bc442d..ffdb5487 100644 --- a/src/shared_types.ts +++ b/src/shared_types.ts @@ -46,6 +46,7 @@ export type XAccount = { archiveTweets: boolean; archiveTweetsHTML: boolean; archiveLikes: boolean; + archiveBookmarks: boolean; archiveDMs: boolean; deleteTweets: boolean; deleteTweetsDaysOldEnabled: boolean; @@ -58,6 +59,7 @@ export type XAccount = { deleteRetweetsDaysOldEnabled: boolean; deleteRetweetsDaysOld: number; deleteLikes: boolean; + deleteBookmarks: boolean; deleteDMs: boolean; unfollowEveryone: boolean; followingCount: number; From 41dbba2b005454e7d6522920662c924c87244a9d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Dec 2024 15:22:31 -0800 Subject: [PATCH 02/25] Add deleteBookmarks setting --- .../src/views/x/XWizardDeleteOptionsPage.vue | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/renderer/src/views/x/XWizardDeleteOptionsPage.vue b/src/renderer/src/views/x/XWizardDeleteOptionsPage.vue index db28c4d6..1bd78026 100644 --- a/src/renderer/src/views/x/XWizardDeleteOptionsPage.vue +++ b/src/renderer/src/views/x/XWizardDeleteOptionsPage.vue @@ -56,6 +56,7 @@ const deleteRetweets = ref(false); const deleteRetweetsDaysOldEnabled = ref(false); const deleteRetweetsDaysOld = ref(0); const deleteLikes = ref(false); +const deleteBookmarks = ref(false); const deleteDMs = ref(false); const unfollowEveryone = ref(false); @@ -74,6 +75,7 @@ const loadSettings = async () => { deleteRetweetsDaysOldEnabled.value = account.xAccount.deleteRetweetsDaysOldEnabled; deleteRetweetsDaysOld.value = account.xAccount.deleteRetweetsDaysOld; deleteLikes.value = account.xAccount.deleteLikes; + deleteBookmarks.value = account.xAccount.deleteBookmarks; deleteDMs.value = account.xAccount.deleteDMs; unfollowEveryone.value = account.xAccount.unfollowEveryone; } @@ -119,6 +121,7 @@ const saveSettings = async () => { account.xAccount.deleteRetweetsDaysOldEnabled = deleteRetweetsDaysOldEnabled.value; account.xAccount.deleteRetweetsDaysOld = deleteRetweetsDaysOld.value; account.xAccount.deleteLikes = deleteLikes.value; + account.xAccount.deleteBookmarks = deleteBookmarks.value; account.xAccount.deleteDMs = deleteDMs.value; account.xAccount.unfollowEveryone = unfollowEveryone.value; @@ -331,6 +334,22 @@ onMounted(async () => { + +
+
+
+ + +
+
+ Premium +
+
+
+
From ce9cd51fb6cd06a0ad76ee1e5e375d84ed51fefa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 12:55:57 -0800 Subject: [PATCH 03/25] Start implementing indexBookmarks, and add a bookmark testdata file --- src/renderer/src/types.ts | 1 + .../src/view_models/AccountXViewModel.ts | 180 + src/shared_types.ts | 8 + testdata/XAPIBookmarks.json | 5507 +++++++++++++++++ 4 files changed, 5696 insertions(+) create mode 100644 testdata/XAPIBookmarks.json diff --git a/src/renderer/src/types.ts b/src/renderer/src/types.ts index a549aa28..76efada3 100644 --- a/src/renderer/src/types.ts +++ b/src/renderer/src/types.ts @@ -18,6 +18,7 @@ export const PlausibleEvents = Object.freeze({ X_JOB_STARTED_INDEX_TWEETS: 'X Job Started: indexTweets', X_JOB_STARTED_ARCHIVE_TWEETS: 'X Job Started: archiveTweets', X_JOB_STARTED_INDEX_LIKES: 'X Job Started: indexLikes', + X_JOB_STARTED_INDEX_BOOKMARKS: 'X Job Started: indexBookmarks', X_JOB_STARTED_INDEX_CONVERSATIONS: 'X Job Started: indexConversations', X_JOB_STARTED_INDEX_MESSAGES: 'X Job Started: indexMessages', X_JOB_STARTED_DELETE_TWEETS: 'X Job Started: deleteTweets', diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/AccountXViewModel.ts index 898b20e8..9bca53e2 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/AccountXViewModel.ts @@ -141,6 +141,9 @@ export class AccountXViewModel extends BaseViewModel { if (this.account?.xAccount?.archiveLikes) { jobTypes.push("indexLikes"); } + if (this.account?.xAccount?.archiveBookmarks) { + jobTypes.push("indexBookmarks"); + } if (this.account?.xAccount?.archiveDMs) { jobTypes.push("indexConversations"); jobTypes.push("indexMessages"); @@ -152,6 +155,9 @@ export class AccountXViewModel extends BaseViewModel { if (this.account?.xAccount?.archiveTweetsHTML) { jobTypes.push("archiveTweets"); } + if (this.account?.xAccount?.archiveBookmarks) { + jobTypes.push("indexBookmarks"); + } if (this.account?.xAccount?.archiveDMs) { jobTypes.push("indexConversations"); jobTypes.push("indexMessages"); @@ -171,6 +177,10 @@ export class AccountXViewModel extends BaseViewModel { jobTypes.push("deleteLikes"); shouldBuildArchive = true; } + if (hasSomeData && this.account?.xAccount?.deleteBookmarks) { + jobTypes.push("deleteBookmarks"); + shouldBuildArchive = true; + } if (this.account?.xAccount?.unfollowEveryone) { jobTypes.push("unfollowEveryone"); } @@ -1624,6 +1634,172 @@ Hang on while I scroll down to your earliest likes.`; return true; } + async runJobIndexBookmarks(jobIndex: number): Promise { + await window.electron.trackEvent(PlausibleEvents.X_JOB_STARTED_INDEX_BOOKMARKS, navigator.userAgent); + + this.showBrowser = true; + this.instructions = `**I'm saving your bookmarks.** + +Hang on while I scroll down to your earliest boomarks.`; + this.showAutomationNotice = true; + + // Start monitoring network requests + await this.loadBlank(); + await window.electron.X.indexStart(this.account?.id); + await this.sleep(2000); + + // Start the progress + this.progress.isIndexBookmarksFinished = false; + this.progress.bookmarksIndexed = 0; + await this.syncProgress(); + + // Load the bookmarks + let errorTriggered = false; + await this.waitForPause(); + await window.electron.X.resetRateLimitInfo(this.account?.id); + await this.loadURLWithRateLimit("https://x.com/" + this.account?.xAccount?.username + "/i/bookmarks"); + await this.sleep(500); + + // Check if likes list is empty + if (await this.doesSelectorExist('div[data-testid="emptyState"]')) { + this.log("runJobIndexLikes", "no likes found"); + this.progress.isIndexLikesFinished = true; + this.progress.likesIndexed = 0; + await this.syncProgress(); + } else { + this.log("runJobIndexLikes", "did not find empty state"); + } + + if (!this.progress.isIndexLikesFinished) { + // Wait for tweets to appear + try { + await this.waitForSelector('article', "https://x.com/" + this.account?.xAccount?.username + "/likes"); + } catch (e) { + this.log("runJobIndexLikes", ["selector never appeared", e]); + if (e instanceof TimeoutError) { + // Were we rate limited? + this.rateLimitInfo = await window.electron.X.isRateLimited(this.account?.id); + if (this.rateLimitInfo.isRateLimited) { + await this.waitForRateLimit(); + } else { + // If the page isn't loading, we assume the user has no likes yet + await this.waitForLoadingToFinish(); + this.progress.isIndexLikesFinished = true; + this.progress.likesIndexed = 0; + await this.syncProgress(); + } + } else if (e instanceof URLChangedError) { + const newURL = this.webview?.getURL(); + await this.error(AutomationErrorType.x_runJob_indexLikes_URLChanged, { + newURL: newURL, + exception: (e as Error).toString() + }) + errorTriggered = true; + } else { + await this.error(AutomationErrorType.x_runJob_indexLikes_OtherError, { + exception: (e as Error).toString() + }) + errorTriggered = true; + } + } + } + + if (errorTriggered) { + await window.electron.X.indexStop(this.account?.id); + return false; + } + + while (this.progress.isIndexLikesFinished === false) { + await this.waitForPause(); + + // Scroll to bottom + await window.electron.X.resetRateLimitInfo(this.account?.id); + let moreToScroll = await this.scrollToBottom(); + this.rateLimitInfo = await window.electron.X.isRateLimited(this.account?.id); + if (this.rateLimitInfo.isRateLimited) { + await this.sleep(500); + await this.scrollToBottom(); + await this.waitForRateLimit(); + if (!await this.indexTweetsHandleRateLimit()) { + // On fail, update the failure state and move on + await window.electron.X.setConfig(this.account?.id, FailureState.indexLikes_FailedToRetryAfterRateLimit, "true"); + break; + } + await this.sleep(500); + moreToScroll = true; + } + + // Parse so far + try { + this.progress = await window.electron.X.indexParseLikes(this.account?.id); + } catch (e) { + const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); + await this.error(AutomationErrorType.x_runJob_indexLikes_ParseTweetsError, { + exception: (e as Error).toString() + }, { + latestResponseData: latestResponseData + }); + errorTriggered = true; + break; + } + this.jobs[jobIndex].progressJSON = JSON.stringify(this.progress); + await window.electron.X.updateJob(this.account?.id, JSON.stringify(this.jobs[jobIndex])); + + // Check if we're done + if (!await window.electron.X.indexIsThereMore(this.account?.id)) { + + // Verify that we're actually done + let verifyResult = true; + try { + verifyResult = await this.indexTweetsVerifyThereIsNoMore(); + } catch (e) { + const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); + await this.error(AutomationErrorType.x_runJob_indexLikes_VerifyThereIsNoMoreError, { + exception: (e as Error).toString() + }, { + latestResponseData: latestResponseData, + currentURL: this.webview?.getURL() + }); + errorTriggered = true; + break; + } + + // If we verified that there are no more tweets, we're done + if (verifyResult) { + this.progress = await window.electron.X.indexLikesFinished(this.account?.id); + + // On success, set the failure state to false + await window.electron.X.setConfig(this.account?.id, FailureState.indexLikes_FailedToRetryAfterRateLimit, "false"); + break; + } + + // Otherwise, update the job and keep going + this.jobs[jobIndex].progressJSON = JSON.stringify(this.progress); + await window.electron.X.updateJob(this.account?.id, JSON.stringify(this.jobs[jobIndex])); + + } else { + if (!moreToScroll) { + // We scrolled to the bottom but we're not finished, so scroll up a bit to trigger infinite scroll next time + await this.sleep(500); + await this.scrollUp(1000); + } + } + + // Check if there is a "Something went wrong" message + await this.indexTweetsCheckForSomethingWrong(); + } + + // Stop monitoring network requests + await window.electron.X.indexStop(this.account?.id); + + if (errorTriggered) { + return false; + } + + await this.finishJob(jobIndex); + return true; + } + async runJobDeleteTweets(jobIndex: number) { await window.electron.trackEvent(PlausibleEvents.X_JOB_STARTED_DELETE_TWEETS, navigator.userAgent); @@ -2336,6 +2512,10 @@ Follow the instructions below to request your archive from X. You will need to v await this.runJobIndexLikes(jobIndex); break; + case "indexBookmarks": + await this.runJobIndexBookmarks(jobIndex); + break; + case "deleteTweets": await this.runJobDeleteTweets(jobIndex); break; diff --git a/src/shared_types.ts b/src/shared_types.ts index ffdb5487..3709e55e 100644 --- a/src/shared_types.ts +++ b/src/shared_types.ts @@ -111,6 +111,7 @@ export type XProgress = { isIndexLikesFinished: boolean; isArchiveTweetsFinished: boolean; isArchiveLikesFinished: boolean; + isIndexBookmarksFinished: boolean; isDeleteTweetsFinished: boolean; isDeleteRetweetsFinished: boolean; isDeleteLikesFinished: boolean; @@ -132,6 +133,9 @@ export type XProgress = { totalLikesToArchive: number; likesArchived: number; + totalBookmarksToIndex: number; + bookmarksIndexed: number; + totalConversations: number; conversationMessagesIndexed: number; @@ -160,6 +164,7 @@ export function emptyXProgress(): XProgress { isIndexLikesFinished: false, isArchiveTweetsFinished: false, isArchiveLikesFinished: false, + isIndexBookmarksFinished: false, isDeleteTweetsFinished: false, isDeleteRetweetsFinished: false, isDeleteLikesFinished: false, @@ -181,6 +186,9 @@ export function emptyXProgress(): XProgress { totalLikesToArchive: 0, likesArchived: 0, + totalBookmarksToIndex: 0, + bookmarksIndexed: 0, + totalConversations: 0, conversationMessagesIndexed: 0, diff --git a/testdata/XAPIBookmarks.json b/testdata/XAPIBookmarks.json new file mode 100644 index 00000000..173db49b --- /dev/null +++ b/testdata/XAPIBookmarks.json @@ -0,0 +1,5507 @@ +{ + "data": { + "bookmark_timeline_v2": { + "timeline": { + "instructions": [ + { + "type": "TimelineAddEntries", + "entries": [ + { + "entryId": "tweet-1866967664587051046", + "sortIndex": "1818267260519218619", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866967664587051046", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxNDkyNjc3NTk5MzkwMzIyNjg5", + "rest_id": "1492677599390322689", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Sun Feb 13 01:50:55 +0000 2022", + "default_profile": true, + "default_profile_image": false, + "description": "memes and shitposts | dm for promo| follow my Bluesky | https://t.co/GyouBrDs9d", + "entities": { + "description": { + "urls": [ + { + "display_url": "boxd.it/537D7", + "expanded_url": "https://boxd.it/537D7", + "url": "https://t.co/GyouBrDs9d", + "indices": [ + 56, + 79 + ] + } + ] + }, + "url": { + "urls": [ + { + "display_url": "bsky.app/profile/nocont…", + "expanded_url": "https://bsky.app/profile/nocontextmeme.bsky.social/post/3l6rs3hdule2q", + "url": "https://t.co/hgwLA9jLvm", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 15833, + "followers_count": 1951041, + "friends_count": 507, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 3306, + "location": "", + "media_count": 13334, + "name": "no context memes", + "normal_followers_count": 1951041, + "pinned_tweet_ids_str": [ + "1868121299433718023" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1492677599390322689/1732022682", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1694331764091564032/My5iC3pg_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "weirddalle", + "statuses_count": 14944, + "translator_type": "none", + "url": "https://t.co/hgwLA9jLvm", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {}, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866967664587051046" + ], + "editable_until_msecs": "1733958352000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "3338083", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 7111, + "bookmarked": true, + "created_at": "Wed Dec 11 22:05:52 +0000 2024", + "conversation_id_str": "1866967664587051046", + "display_text_range": [ + 0, + 0 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/1tiOpEacxZ", + "expanded_url": "https://x.com/weirddalle/status/1866967664587051046/photo/1", + "id_str": "1866967660740698112", + "indices": [ + 0, + 23 + ], + "media_key": "3_1866967660740698112", + "media_url_https": "https://pbs.twimg.com/media/GejNMiGXYAAN474.jpg", + "type": "photo", + "url": "https://t.co/1tiOpEacxZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + }, + "medium": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 320, + "y": 278, + "h": 48, + "w": 48 + }, + { + "x": 249, + "y": 287, + "h": 105, + "w": 105 + } + ] + }, + "orig": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + } + }, + "sizes": { + "large": { + "h": 824, + "w": 720, + "resize": "fit" + }, + "medium": { + "h": 824, + "w": 720, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 594, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 824, + "width": 720, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 720, + "h": 403 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 720 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 821 + }, + { + "x": 20, + "y": 0, + "w": 412, + "h": 824 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 824 + } + ] + }, + "media_results": { + "result": { + "media_key": "3_1866967660740698112" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/1tiOpEacxZ", + "expanded_url": "https://x.com/weirddalle/status/1866967664587051046/photo/1", + "id_str": "1866967660740698112", + "indices": [ + 0, + 23 + ], + "media_key": "3_1866967660740698112", + "media_url_https": "https://pbs.twimg.com/media/GejNMiGXYAAN474.jpg", + "type": "photo", + "url": "https://t.co/1tiOpEacxZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + }, + "medium": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 320, + "y": 278, + "h": 48, + "w": 48 + }, + { + "x": 249, + "y": 287, + "h": 105, + "w": 105 + } + ] + }, + "orig": { + "faces": [ + { + "x": 388, + "y": 337, + "h": 59, + "w": 59 + }, + { + "x": 302, + "y": 349, + "h": 128, + "w": 128 + } + ] + } + }, + "sizes": { + "large": { + "h": 824, + "w": 720, + "resize": "fit" + }, + "medium": { + "h": 824, + "w": 720, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 594, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 824, + "width": 720, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 720, + "h": 403 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 720 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 821 + }, + { + "x": 20, + "y": 0, + "w": 412, + "h": 824 + }, + { + "x": 0, + "y": 0, + "w": 720, + "h": 824 + } + ] + }, + "media_results": { + "result": { + "media_key": "3_1866967660740698112" + } + } + } + ] + }, + "favorite_count": 225830, + "favorited": false, + "full_text": "https://t.co/1tiOpEacxZ", + "is_quote_status": false, + "lang": "zxx", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 97, + "reply_count": 122, + "retweet_count": 9432, + "retweeted": false, + "user_id_str": "1492677599390322689", + "id_str": "1866967664587051046" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1866607785460543507", + "sortIndex": "1818267214026123177", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866607785460543507", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxMDY5NjI0NzE0MDAyMzQxODg4", + "rest_id": "1069624714002341888", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Mon Dec 03 16:09:39 +0000 2018", + "default_profile": true, + "default_profile_image": false, + "description": "checkout my ceramics page: @yamzarchive ٩(•̤̀ᵕ•̤́๑)ᵒᵏᵎᵎᵎᵎ Youtube debut 2025 | เรียนไทยอยู่ฮะ ㅇㅅㅇ", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "instagram.com/yamzarchive?ig…", + "expanded_url": "https://www.instagram.com/yamzarchive?igsh=MTIzbnhpZWMwdmpieA%3D%3D&utm_source=qr", + "url": "https://t.co/IJESjR3xeN", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 85078, + "followers_count": 59404, + "friends_count": 373, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 69, + "location": "", + "media_count": 3170, + "name": "yammi", + "normal_followers_count": 59404, + "pinned_tweet_ids_str": [ + "1866685376368111657" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1069624714002341888/1730165802", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1852394652428562432/K50f2Q_g_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "sighyam", + "statuses_count": 42673, + "translator_type": "none", + "url": "https://t.co/IJESjR3xeN", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": { + "is_enabled": true + } + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866607785460543507" + ], + "editable_until_msecs": "1733872550000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "37329295", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 45652, + "bookmarked": true, + "created_at": "Tue Dec 10 22:15:50 +0000 2024", + "conversation_id_str": "1866607785460543507", + "display_text_range": [ + 0, + 82 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/qqqe8wOsTP", + "expanded_url": "https://x.com/sighyam/status/1866607785460543507/photo/1", + "id_str": "1866607776723517440", + "indices": [ + 83, + 106 + ], + "media_key": "3_1866607776723517440", + "media_url_https": "https://pbs.twimg.com/media/GeeF4hqWQAAGW6K.jpg", + "type": "photo", + "url": "https://t.co/qqqe8wOsTP", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 1151, + "y": 675, + "h": 73, + "w": 73 + } + ] + }, + "medium": { + "faces": [ + { + "x": 698, + "y": 409, + "h": 44, + "w": 44 + } + ] + }, + "small": { + "faces": [ + { + "x": 396, + "y": 232, + "h": 25, + "w": 25 + } + ] + }, + "orig": { + "faces": [ + { + "x": 1151, + "y": 675, + "h": 73, + "w": 73 + } + ] + } + }, + "sizes": { + "large": { + "h": 1975, + "w": 1319, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 801, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 454, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1975, + "width": 1319, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1319, + "h": 739 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1319 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1504 + }, + { + "x": 48, + "y": 0, + "w": 988, + "h": 1975 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1975 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866607776723517440" + } + } + }, + { + "display_url": "pic.x.com/qqqe8wOsTP", + "expanded_url": "https://x.com/sighyam/status/1866607785460543507/photo/1", + "id_str": "1866607776757100544", + "indices": [ + 83, + 106 + ], + "media_key": "3_1866607776757100544", + "media_url_https": "https://pbs.twimg.com/media/GeeF4hyWsAAfDlc.jpg", + "type": "photo", + "url": "https://t.co/qqqe8wOsTP", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 4032, + "width": 3024, + "focus_rects": [ + { + "x": 0, + "y": 666, + "w": 3024, + "h": 1693 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 3024 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 3447 + }, + { + "x": 302, + "y": 0, + "w": 2016, + "h": 4032 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 4032 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866607776757100544" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/qqqe8wOsTP", + "expanded_url": "https://x.com/sighyam/status/1866607785460543507/photo/1", + "id_str": "1866607776723517440", + "indices": [ + 83, + 106 + ], + "media_key": "3_1866607776723517440", + "media_url_https": "https://pbs.twimg.com/media/GeeF4hqWQAAGW6K.jpg", + "type": "photo", + "url": "https://t.co/qqqe8wOsTP", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 1151, + "y": 675, + "h": 73, + "w": 73 + } + ] + }, + "medium": { + "faces": [ + { + "x": 698, + "y": 409, + "h": 44, + "w": 44 + } + ] + }, + "small": { + "faces": [ + { + "x": 396, + "y": 232, + "h": 25, + "w": 25 + } + ] + }, + "orig": { + "faces": [ + { + "x": 1151, + "y": 675, + "h": 73, + "w": 73 + } + ] + } + }, + "sizes": { + "large": { + "h": 1975, + "w": 1319, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 801, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 454, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1975, + "width": 1319, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1319, + "h": 739 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1319 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1504 + }, + { + "x": 48, + "y": 0, + "w": 988, + "h": 1975 + }, + { + "x": 0, + "y": 0, + "w": 1319, + "h": 1975 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866607776723517440" + } + } + }, + { + "display_url": "pic.x.com/qqqe8wOsTP", + "expanded_url": "https://x.com/sighyam/status/1866607785460543507/photo/1", + "id_str": "1866607776757100544", + "indices": [ + 83, + 106 + ], + "media_key": "3_1866607776757100544", + "media_url_https": "https://pbs.twimg.com/media/GeeF4hyWsAAfDlc.jpg", + "type": "photo", + "url": "https://t.co/qqqe8wOsTP", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 4032, + "width": 3024, + "focus_rects": [ + { + "x": 0, + "y": 666, + "w": 3024, + "h": 1693 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 3024 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 3447 + }, + { + "x": 302, + "y": 0, + "w": 2016, + "h": 4032 + }, + { + "x": 0, + "y": 0, + "w": 3024, + "h": 4032 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866607776757100544" + } + } + } + ] + }, + "favorite_count": 708709, + "favorited": false, + "full_text": "the chinese grandpa in my building just dropped off a box of chicken feet for me \uD83D\uDE2D https://t.co/qqqe8wOsTP", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 5840, + "reply_count": 3025, + "retweet_count": 31307, + "retweeted": false, + "user_id_str": "1069624714002341888", + "id_str": "1866607785460543507" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867018733987958978", + "sortIndex": "1818267199240380443", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867018733987958978", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjozMjQwMzMwODQ0", + "rest_id": "3240330844", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Thu May 07 11:41:29 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "Purdue | @/JamesGetsPolitical on TikTok | #FreePalestine\uD83C\uDDF5\uD83C\uDDF8 | Marxist Anti-Imperialist | Bylines in @Mondoweiss", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "youtube.com/@JamesGetsPoli…", + "expanded_url": "https://youtube.com/@JamesGetsPolitical?si=ygDWZ5w1Oy7pbBBR", + "url": "https://t.co/iWOIfn3nyk", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 241552, + "followers_count": 47583, + "friends_count": 1330, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 287, + "location": "Occupied Lenape Territory", + "media_count": 5614, + "name": "James Ray \uD83D\uDD3B", + "normal_followers_count": 47583, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/3240330844/1691300098", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1716230117804883968/g0xHK0jg_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "GoodVibePolitik", + "statuses_count": 83695, + "translator_type": "none", + "url": "https://t.co/iWOIfn3nyk", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867018733987958978" + ], + "editable_until_msecs": "1733970528000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "3505357", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 2788, + "bookmarked": true, + "created_at": "Thu Dec 12 01:28:48 +0000 2024", + "conversation_id_str": "1867018733987958978", + "display_text_range": [ + 0, + 112 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/PI6slolI28", + "expanded_url": "https://x.com/GoodVibePolitik/status/1867018733987958978/photo/1", + "id_str": "1867018729151778817", + "indices": [ + 113, + 136 + ], + "media_key": "3_1867018729151778817", + "media_url_https": "https://pbs.twimg.com/media/Gej7pGwX0AEh6Ml.jpg", + "type": "photo", + "url": "https://t.co/PI6slolI28", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 972, + "y": 690, + "h": 184, + "w": 184 + } + ] + }, + "medium": { + "faces": [ + { + "x": 569, + "y": 404, + "h": 107, + "w": 107 + } + ] + }, + "small": { + "faces": [ + { + "x": 322, + "y": 229, + "h": 61, + "w": 61 + } + ] + }, + "orig": { + "faces": [ + { + "x": 972, + "y": 690, + "h": 184, + "w": 184 + } + ] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 338, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 51, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867018729151778817" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/PI6slolI28", + "expanded_url": "https://x.com/GoodVibePolitik/status/1867018733987958978/photo/1", + "id_str": "1867018729151778817", + "indices": [ + 113, + 136 + ], + "media_key": "3_1867018729151778817", + "media_url_https": "https://pbs.twimg.com/media/Gej7pGwX0AEh6Ml.jpg", + "type": "photo", + "url": "https://t.co/PI6slolI28", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 972, + "y": 690, + "h": 184, + "w": 184 + } + ] + }, + "medium": { + "faces": [ + { + "x": 569, + "y": 404, + "h": 107, + "w": 107 + } + ] + }, + "small": { + "faces": [ + { + "x": 322, + "y": 229, + "h": 61, + "w": 61 + } + ] + }, + "orig": { + "faces": [ + { + "x": 972, + "y": 690, + "h": 184, + "w": 184 + } + ] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 338, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 51, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867018729151778817" + } + } + } + ] + }, + "favorite_count": 251841, + "favorited": false, + "full_text": "Shoutout my buddy Luigi for hitting the local gym with me on December 4th from 6:00am to 12:00pm great chest day https://t.co/PI6slolI28", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 146, + "reply_count": 219, + "retweet_count": 11066, + "retweeted": false, + "user_id_str": "3240330844", + "id_str": "1867018733987958978" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1866911837117145570", + "sortIndex": "1818267190639376970", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866911837117145570", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo3ODE1MzY5NTQ3NDM2NzY5Mjg=", + "rest_id": "781536954743676928", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Thu Sep 29 16:51:45 +0000 2016", + "default_profile": true, + "default_profile_image": false, + "description": "writer on books and film, phd in literary representations of textile labour + its products, but an lfc fan before most things jessicafwhite@hotmail.co.uk", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "jessicafwhite.wixsite.com/jesswhite", + "expanded_url": "https://jessicafwhite.wixsite.com/jesswhite", + "url": "https://t.co/F5Pdxb5fyy", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 113642, + "followers_count": 5389, + "friends_count": 851, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 16, + "location": "liverpool", + "media_count": 5116, + "name": "jess white", + "normal_followers_count": 5389, + "pinned_tweet_ids_str": [ + "1642841284002938883" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/781536954743676928/1726147037", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1814757335706828800/UFmOz5j2_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "jessf_white", + "statuses_count": 32037, + "translator_type": "none", + "url": "https://t.co/F5Pdxb5fyy", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866911837117145570" + ], + "editable_until_msecs": "1733945042000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "4698439", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 16966, + "bookmarked": true, + "created_at": "Wed Dec 11 18:24:02 +0000 2024", + "conversation_id_str": "1866911837117145570", + "display_text_range": [ + 0, + 66 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/boKODsP9qU", + "expanded_url": "https://x.com/jessf_white/status/1866911837117145570/photo/1", + "id_str": "1866911832239181824", + "indices": [ + 67, + 90 + ], + "media_key": "3_1866911832239181824", + "media_url_https": "https://pbs.twimg.com/media/Geiaa4uXEAA186U.jpg", + "type": "photo", + "url": "https://t.co/boKODsP9qU", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1152, + "w": 1170, + "resize": "fit" + }, + "medium": { + "h": 1152, + "w": 1170, + "resize": "fit" + }, + "small": { + "h": 670, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1152, + "width": 1170, + "focus_rects": [ + { + "x": 0, + "y": 497, + "w": 1170, + "h": 655 + }, + { + "x": 18, + "y": 0, + "w": 1152, + "h": 1152 + }, + { + "x": 109, + "y": 0, + "w": 1011, + "h": 1152 + }, + { + "x": 326, + "y": 0, + "w": 576, + "h": 1152 + }, + { + "x": 0, + "y": 0, + "w": 1170, + "h": 1152 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866911832239181824" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/boKODsP9qU", + "expanded_url": "https://x.com/jessf_white/status/1866911837117145570/photo/1", + "id_str": "1866911832239181824", + "indices": [ + 67, + 90 + ], + "media_key": "3_1866911832239181824", + "media_url_https": "https://pbs.twimg.com/media/Geiaa4uXEAA186U.jpg", + "type": "photo", + "url": "https://t.co/boKODsP9qU", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1152, + "w": 1170, + "resize": "fit" + }, + "medium": { + "h": 1152, + "w": 1170, + "resize": "fit" + }, + "small": { + "h": 670, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1152, + "width": 1170, + "focus_rects": [ + { + "x": 0, + "y": 497, + "w": 1170, + "h": 655 + }, + { + "x": 18, + "y": 0, + "w": 1152, + "h": 1152 + }, + { + "x": 109, + "y": 0, + "w": 1011, + "h": 1152 + }, + { + "x": 326, + "y": 0, + "w": 576, + "h": 1152 + }, + { + "x": 0, + "y": 0, + "w": 1170, + "h": 1152 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866911832239181824" + } + } + } + ] + }, + "favorite_count": 237327, + "favorited": false, + "full_text": "you MUST see this illustration from baby kermit’s christmas (1988) https://t.co/boKODsP9qU", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 383, + "reply_count": 175, + "retweet_count": 21508, + "retweeted": false, + "user_id_str": "781536954743676928", + "id_str": "1866911837117145570" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867217847878820265", + "sortIndex": "1818267184207297384", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867217847878820265", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxNjczODkwNA==", + "rest_id": "16738904", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Tue Oct 14 14:49:23 +0000 2008", + "default_profile": false, + "default_profile_image": false, + "description": "yappalicious hermitpilled cringemaxxed optimismcore oft-plagiarized brainrot/bookworm-dualmoding unmasked masker woman of slut experience & leftist aggression", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "cash.app/$suchpay", + "expanded_url": "https://cash.app/$suchpay", + "url": "https://t.co/6CT1P4cprB", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 213736, + "followers_count": 27033, + "friends_count": 827, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 216, + "location": "not a bit! i’m just like this.", + "media_count": 17438, + "name": "Vivian", + "normal_followers_count": 27033, + "pinned_tweet_ids_str": [ + "1677808601602170880" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/16738904/1599638694", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1750921465543655430/NAEvgcua_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "suchnerve", + "statuses_count": 150066, + "translator_type": "none", + "url": "https://t.co/6CT1P4cprB", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867217847878820265" + ], + "editable_until_msecs": "1734018000000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "8545454", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 22119, + "bookmarked": true, + "created_at": "Thu Dec 12 14:40:00 +0000 2024", + "conversation_id_str": "1867217847878820265", + "display_text_range": [ + 0, + 145 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 540031, + "favorited": false, + "full_text": "Violence is NOT the answer. The answer is *opens history book*\n\nuh oh\n\n*frantically starts flipping through pages*\n\nuh oh. oh no. no no no. uh oh", + "is_quote_status": false, + "lang": "en", + "quote_count": 931, + "reply_count": 962, + "retweet_count": 60289, + "retweeted": false, + "user_id_str": "16738904", + "id_str": "1867217847878820265" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1866877533909815710", + "sortIndex": "1818267172594708152", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866877533909815710", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxNTMxODE2Njk5MTA3ODg1MDU3", + "rest_id": "1531816699107885057", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Wed Jun 01 01:56:03 +0000 2022", + "default_profile": true, + "default_profile_image": false, + "description": "first as tragedy :-( then as farce :~) teen inventor of cancel culture, publishing industry dropout, book dedication archivist @dedication_bot she/her", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "instagram.com/adult.goth", + "expanded_url": "https://www.instagram.com/adult.goth", + "url": "https://t.co/Fk094er4tt", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 318446, + "followers_count": 19284, + "friends_count": 2679, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 52, + "location": "brooklyn", + "media_count": 2203, + "name": "multitude\uD83D\uDD3Bcontainer", + "normal_followers_count": 19284, + "pinned_tweet_ids_str": [ + "1867313490979099035" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1531816699107885057/1721916515", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1868318425904271360/C21VM3rr_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "lllliatttt", + "statuses_count": 24705, + "translator_type": "none", + "url": "https://t.co/Fk094er4tt", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866877533909815710" + ], + "editable_until_msecs": "1733936863000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "3966521", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 4093, + "bookmarked": true, + "created_at": "Wed Dec 11 16:07:43 +0000 2024", + "conversation_id_str": "1866877533909815710", + "display_text_range": [ + 0, + 48 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/2gxX9yUDJG", + "expanded_url": "https://x.com/lllliatttt/status/1866877533909815710/photo/1", + "id_str": "1866877529862098944", + "indices": [ + 49, + 72 + ], + "media_key": "3_1866877529862098944", + "media_url_https": "https://pbs.twimg.com/media/Geh7OOaW0AAvce8.jpg", + "type": "photo", + "url": "https://t.co/2gxX9yUDJG", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 51, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866877529862098944" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/2gxX9yUDJG", + "expanded_url": "https://x.com/lllliatttt/status/1866877533909815710/photo/1", + "id_str": "1866877529862098944", + "indices": [ + 49, + 72 + ], + "media_key": "3_1866877529862098944", + "media_url_https": "https://pbs.twimg.com/media/Geh7OOaW0AAvce8.jpg", + "type": "photo", + "url": "https://t.co/2gxX9yUDJG", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 51, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866877529862098944" + } + } + } + ] + }, + "favorite_count": 143766, + "favorited": false, + "full_text": "the voice of the people cries out for revolution https://t.co/2gxX9yUDJG", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 738, + "reply_count": 216, + "retweet_count": 12726, + "retweeted": false, + "user_id_str": "1531816699107885057", + "id_str": "1866877533909815710" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867207722602790979", + "sortIndex": "1818267128040913072", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867207722602790979", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo1ODU3OTk0Mg==", + "rest_id": "58579942", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": false, + "can_media_tag": true, + "created_at": "Mon Jul 20 20:24:48 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "Florida Man", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "v.cameo.com/e/hGKCsOeZJOb", + "expanded_url": "https://v.cameo.com/e/hGKCsOeZJOb", + "url": "https://t.co/AVf1cOUQqu", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 26927, + "followers_count": 3170718, + "friends_count": 3954, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 7318, + "location": "Find me on CAMEO! ", + "media_count": 1928, + "name": "Matt Gaetz", + "normal_followers_count": 3170718, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/58579942/1731556384", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1832034268077338625/v_sJ7qw8_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "mattgaetz", + "statuses_count": 26919, + "translator_type": "none", + "url": "https://t.co/AVf1cOUQqu", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {}, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867207722602790979" + ], + "editable_until_msecs": "1734015586000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "1436120", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 1149, + "bookmarked": true, + "created_at": "Thu Dec 12 13:59:46 +0000 2024", + "conversation_id_str": "1867207722602790979", + "display_text_range": [ + 0, + 44 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/V2beqPXkyo", + "expanded_url": "https://x.com/mattgaetz/status/1867207722602790979/photo/1", + "id_str": "1867207718987087872", + "indices": [ + 45, + 68 + ], + "media_key": "3_1867207718987087872", + "media_url_https": "https://pbs.twimg.com/media/GemnhwwXwAAXA8d.jpg", + "type": "photo", + "url": "https://t.co/V2beqPXkyo", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + }, + "medium": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + }, + "small": { + "faces": [ + { + "x": 331, + "y": 188, + "h": 33, + "w": 33 + }, + { + "x": 220, + "y": 118, + "h": 100, + "w": 100 + } + ] + }, + "orig": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + } + }, + "sizes": { + "large": { + "h": 908, + "w": 681, + "resize": "fit" + }, + "medium": { + "h": 908, + "w": 681, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 908, + "width": 681, + "focus_rects": [ + { + "x": 0, + "y": 59, + "w": 681, + "h": 381 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 681 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 776 + }, + { + "x": 113, + "y": 0, + "w": 454, + "h": 908 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 908 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867207718987087872" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/V2beqPXkyo", + "expanded_url": "https://x.com/mattgaetz/status/1867207722602790979/photo/1", + "id_str": "1867207718987087872", + "indices": [ + 45, + 68 + ], + "media_key": "3_1867207718987087872", + "media_url_https": "https://pbs.twimg.com/media/GemnhwwXwAAXA8d.jpg", + "type": "photo", + "url": "https://t.co/V2beqPXkyo", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + }, + "medium": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + }, + "small": { + "faces": [ + { + "x": 331, + "y": 188, + "h": 33, + "w": 33 + }, + { + "x": 220, + "y": 118, + "h": 100, + "w": 100 + } + ] + }, + "orig": { + "faces": [ + { + "x": 442, + "y": 252, + "h": 45, + "w": 45 + }, + { + "x": 294, + "y": 158, + "h": 134, + "w": 134 + } + ] + } + }, + "sizes": { + "large": { + "h": 908, + "w": 681, + "resize": "fit" + }, + "medium": { + "h": 908, + "w": 681, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 908, + "width": 681, + "focus_rects": [ + { + "x": 0, + "y": 59, + "w": 681, + "h": 381 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 681 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 776 + }, + { + "x": 113, + "y": 0, + "w": 454, + "h": 908 + }, + { + "x": 0, + "y": 0, + "w": 681, + "h": 908 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867207718987087872" + } + } + } + ] + }, + "favorite_count": 134122, + "favorited": false, + "full_text": "Looks like he’s unburdened by what has been. https://t.co/V2beqPXkyo", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 601, + "reply_count": 3726, + "retweet_count": 16593, + "retweeted": false, + "user_id_str": "58579942", + "id_str": "1867207722602790979" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1866719996056879357", + "sortIndex": "1818267052782575225", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866719996056879357", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxMTUzNjAzMjc1MzA2MDMzMTUy", + "rest_id": "1153603275306033152", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": false, + "can_media_tag": false, + "created_at": "Tue Jul 23 09:50:28 +0000 2019", + "default_profile": false, + "default_profile_image": false, + "description": "Hampton University Alum, N.U.P.E.\uD83D\uDC4C\uD83C\uDFFE! Bx Resident! Behavioral Specialist! Sociologist by degree! This is FAFO season.. Brooklyn born! GT Massive \uD83C\uDDEC\uD83C\uDDFE", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 141900, + "followers_count": 75228, + "friends_count": 17524, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 303, + "location": "Bronx, NY", + "media_count": 6621, + "name": "\uD83D\uDC51 Mr. Weeks \uD83D\uDC51", + "normal_followers_count": 75228, + "pinned_tweet_ids_str": [ + "1368355030223437834" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1153603275306033152/1674112710", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1615971247132758016/gW21QcRR_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "WonderKing82", + "statuses_count": 196644, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866719996056879357" + ], + "editable_until_msecs": "1733899303000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "5887934", + "state": "EnabledWithCount" + }, + "source": "Twitter for Android", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1866622266890477955", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo4NDQzMjM5MTI4MDg5MTA4NDk=", + "rest_id": "844323912808910849", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Tue Mar 21 23:04:42 +0000 2017", + "default_profile": true, + "default_profile_image": false, + "description": "\uD83D\uDC99 Delay | Deny | Depose | Baskin Robbins ALWAYS finds out | Partnered with Epic Games | Praying the Orange One has a Stroke\uD83D\uDC99", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 2577, + "followers_count": 224, + "friends_count": 343, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 1, + "location": "Railing Maye Musk", + "media_count": 223, + "name": "Ⓧ - \uD835\uDE4E\uD835\uDE58\uD835\uDE64\uD835\uDE69\uD835\uDE69 \uD835\uDE4E\uD835\uDE6A\uD835\uDE62\uD835\uDE62\uD835\uDE5A\uD835\uDE67\uD835\uDE68 - Ⓧ", + "normal_followers_count": 224, + "pinned_tweet_ids_str": [ + "1715925548935823422" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/844323912808910849/1723257668", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1822099134179926017/OVAWwQZd_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "Sigma_Fisch", + "statuses_count": 1625, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866622266890477955" + ], + "editable_until_msecs": "1733876003000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "6128112", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 1031, + "bookmarked": false, + "created_at": "Tue Dec 10 23:13:23 +0000 2024", + "conversation_id_str": "1866533374237810747", + "display_text_range": [ + 17, + 49 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/ypdd60P9jN", + "expanded_url": "https://x.com/Sigma_Fisch/status/1866622266890477955/video/1", + "id_str": "1866622242110459904", + "indices": [ + 50, + 73 + ], + "media_key": "7_1866622242110459904", + "media_url_https": "https://pbs.twimg.com/ext_tw_video_thumb/1866622242110459904/pu/img/oGzrUrxv1imYobC3.jpg", + "type": "video", + "url": "https://t.co/ypdd60P9jN", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 598, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 598, + "w": 1080, + "resize": "fit" + }, + "small": { + "h": 377, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 598, + "width": 1080, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 540, + 299 + ], + "duration_millis": 16215, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/pl/YXbbEhSpf0BK9R5_.m3u8?tag=12&v=cfc" + }, + { + "bitrate": 256000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/486x270/20JvfSEeqjOyItdW.mp4?tag=12" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/650x360/MdhSqPRkYCpY6FsB.mp4?tag=12" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/1080x598/Cbnhso8VUyzQX8jC.mp4?tag=12" + } + ] + }, + "media_results": { + "result": { + "media_key": "7_1866622242110459904" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [ + { + "id_str": "941538966871912448", + "name": "Patricia Yamane", + "screen_name": "yamane_patricia", + "indices": [ + 0, + 16 + ] + } + ] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/ypdd60P9jN", + "expanded_url": "https://x.com/Sigma_Fisch/status/1866622266890477955/video/1", + "id_str": "1866622242110459904", + "indices": [ + 50, + 73 + ], + "media_key": "7_1866622242110459904", + "media_url_https": "https://pbs.twimg.com/ext_tw_video_thumb/1866622242110459904/pu/img/oGzrUrxv1imYobC3.jpg", + "type": "video", + "url": "https://t.co/ypdd60P9jN", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 598, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 598, + "w": 1080, + "resize": "fit" + }, + "small": { + "h": 377, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 598, + "width": 1080, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 540, + 299 + ], + "duration_millis": 16215, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/pl/YXbbEhSpf0BK9R5_.m3u8?tag=12&v=cfc" + }, + { + "bitrate": 256000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/486x270/20JvfSEeqjOyItdW.mp4?tag=12" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/650x360/MdhSqPRkYCpY6FsB.mp4?tag=12" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1866622242110459904/pu/vid/avc1/1080x598/Cbnhso8VUyzQX8jC.mp4?tag=12" + } + ] + }, + "media_results": { + "result": { + "media_key": "7_1866622242110459904" + } + } + } + ] + }, + "favorite_count": 6927, + "favorited": false, + "full_text": "@yamane_patricia There were complaints of a smell https://t.co/ypdd60P9jN", + "in_reply_to_screen_name": "yamane_patricia", + "in_reply_to_status_id_str": "1866533374237810747", + "in_reply_to_user_id_str": "941538966871912448", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 348, + "reply_count": 247, + "retweet_count": 554, + "retweeted": false, + "user_id_str": "844323912808910849", + "id_str": "1866622266890477955" + } + } + }, + "legacy": { + "bookmark_count": 7763, + "bookmarked": true, + "created_at": "Wed Dec 11 05:41:43 +0000 2024", + "conversation_id_str": "1866719996056879357", + "display_text_range": [ + 0, + 28 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 157063, + "favorited": false, + "full_text": "Did he shit his pants lmaooo", + "is_quote_status": true, + "lang": "en", + "quote_count": 243, + "quoted_status_id_str": "1866622266890477955", + "quoted_status_permalink": { + "url": "https://t.co/bHTxEmMvqi", + "expanded": "https://twitter.com/Sigma_Fisch/status/1866622266890477955", + "display": "x.com/Sigma_Fisch/st…" + }, + "reply_count": 1626, + "retweet_count": 8259, + "retweeted": false, + "user_id_str": "1153603275306033152", + "id_str": "1866719996056879357" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867195091632488622", + "sortIndex": "1818267046338397776", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867195091632488622", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo5NzE4NDE4NTA2OTU5NzkwMTA=", + "rest_id": "971841850695979010", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Thu Mar 08 20:15:27 +0000 2018", + "default_profile": false, + "default_profile_image": false, + "description": "Sav with one “n” - Protest coverage - Political commentary - Proud American - Formerly 3x Banned On Twitter | Reporter with @TPUSA | Writer for @TPostMillennial", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "linktr.ee/savsays", + "expanded_url": "https://linktr.ee/savsays", + "url": "https://t.co/QeVH42lFoF", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 37718, + "followers_count": 611099, + "friends_count": 1011, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 1410, + "location": "Texas", + "media_count": 950, + "name": "Savanah Hernandez", + "normal_followers_count": 611099, + "pinned_tweet_ids_str": [ + "1867195091632488622" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/971841850695979010/1678842597", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1816878006637322240/aFxgD_ws_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "sav_says_", + "statuses_count": 6422, + "translator_type": "none", + "url": "https://t.co/QeVH42lFoF", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1673830788758945793", + "professional_type": "Creator", + "category": [] + }, + "tipjar_settings": { + "is_enabled": true, + "cash_app_handle": "savsays" + }, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867195091632488622" + ], + "editable_until_msecs": "1734012575000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "7174411", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "note_tweet": { + "is_expandable": true, + "note_tweet_results": { + "result": { + "id": "Tm90ZVR3ZWV0OjE4NjcxOTUwOTE1MzU5NDE2MzI=", + "text": "This morning a TSA agent tried to bully me into getting my photo taken after I tried to opt-out of the facial recognition software that TSA is trying to implement. \n\nApparently it’s the norm now to get your photo taken on top of giving TSA your ID every time you want to get on a flight. \n\nTo add insult to injury, 2 illegals went through TSA right before me and didn’t have to undergo facial recognition. \n\nYour daily reminder that we shouldn’t even have to be opting out of this and forcing Americans into facial recognition before getting on a flight isn’t normal!", + "entity_set": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + } + } + } + }, + "legacy": { + "bookmark_count": 4335, + "bookmarked": true, + "created_at": "Thu Dec 12 13:09:35 +0000 2024", + "conversation_id_str": "1867195091632488622", + "display_text_range": [ + 0, + 279 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/UtGyIVuRTV", + "expanded_url": "https://x.com/sav_says_/status/1867195091632488622/video/1", + "id_str": "1867194982634799104", + "indices": [ + 280, + 303 + ], + "media_key": "13_1867194982634799104", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1867194982634799104/img/yuGB9DKClEGMz1Yq.jpg", + "type": "video", + "url": "https://t.co/UtGyIVuRTV", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 82000, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/pl/6-bDCdYFO1YsvHkp.m3u8?tag=16&v=82f" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/480x270/yW5-OvBptmKYOvs0.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/640x360/9zOxp5MB1PrB98EQ.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/1280x720/myy4P_Hcdz3mLuKa.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1867194982634799104" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/UtGyIVuRTV", + "expanded_url": "https://x.com/sav_says_/status/1867195091632488622/video/1", + "id_str": "1867194982634799104", + "indices": [ + 280, + 303 + ], + "media_key": "13_1867194982634799104", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1867194982634799104/img/yuGB9DKClEGMz1Yq.jpg", + "type": "video", + "url": "https://t.co/UtGyIVuRTV", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 82000, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/pl/6-bDCdYFO1YsvHkp.m3u8?tag=16&v=82f" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/480x270/yW5-OvBptmKYOvs0.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/640x360/9zOxp5MB1PrB98EQ.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867194982634799104/vid/avc1/1280x720/myy4P_Hcdz3mLuKa.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1867194982634799104" + } + } + } + ] + }, + "favorite_count": 56944, + "favorited": false, + "full_text": "This morning a TSA agent tried to bully me into getting my photo taken after I tried to opt-out of the facial recognition software that TSA is trying to implement. \n\nApparently it’s the norm now to get your photo taken on top of giving TSA your ID every time you want to get on a https://t.co/UtGyIVuRTV", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 997, + "reply_count": 4109, + "retweet_count": 16599, + "retweeted": false, + "user_id_str": "971841850695979010", + "id_str": "1867195091632488622" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867029292565090679", + "sortIndex": "1818267036617859694", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867029292565090679", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxMDQ3MTIyMTg5Njg0NTkyNjQw", + "rest_id": "1047122189684592640", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Tue Oct 02 13:52:39 +0000 2018", + "default_profile": false, + "default_profile_image": false, + "description": "Hi! I love monster hunter, pokemon and many other things. making a game (very slowly) in my free time called decrowned! she/her blm", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 211480, + "followers_count": 4331, + "friends_count": 1774, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 27, + "location": "i need plushie alatreon now ", + "media_count": 10062, + "name": "paige : mh2/mh3 enjoyer", + "normal_followers_count": 4331, + "pinned_tweet_ids_str": [ + "1791559344539988156" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1047122189684592640/1675827955", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1819546589356052480/s7NKNQWl_normal.png", + "profile_interstitial_type": "", + "screen_name": "gandalfsoda", + "statuses_count": 64015, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867029292565090679" + ], + "editable_until_msecs": "1733973045000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "6116300", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1866875179789394001", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo4NzE4MDc0MjY4Mzk5Mjg4MzU=", + "rest_id": "871807426839928835", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Mon Jun 05 19:14:22 +0000 2017", + "default_profile": false, + "default_profile_image": false, + "description": "https://t.co/fkowFCa1h6 \nDaemon Prince of Malice +18 \nPartnered w/ @ADVANCEDgg \nVtuber Model: @BestiaSilvanus \nRigging: Maxasada\nWife: @NekoRose93", + "entities": { + "description": { + "urls": [ + { + "display_url": "twitch.tv/KillersawVT", + "expanded_url": "http://twitch.tv/KillersawVT", + "url": "https://t.co/fkowFCa1h6", + "indices": [ + 0, + 23 + ] + } + ] + }, + "url": { + "urls": [ + { + "display_url": "killersawvt.carrd.co", + "expanded_url": "https://killersawvt.carrd.co", + "url": "https://t.co/W5Vepu2ivt", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 9549, + "followers_count": 2451, + "friends_count": 2492, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 28, + "location": "The Warp", + "media_count": 4390, + "name": "⛧Killersaw⛧", + "normal_followers_count": 2451, + "pinned_tweet_ids_str": [ + "1863692876074422321" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/871807426839928835/1729832371", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1832265762708267008/67aCx-jo_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "killersawgaming", + "statuses_count": 12162, + "translator_type": "none", + "url": "https://t.co/W5Vepu2ivt", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1536937148628312064", + "professional_type": "Creator", + "category": [ + { + "id": 934, + "name": "Social Media Influencer", + "icon_name": "IconBriefcaseStroke" + } + ] + }, + "tipjar_settings": { + "is_enabled": true + } + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866875179789394001" + ], + "editable_until_msecs": "1733936302000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "8542833", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 7403, + "bookmarked": false, + "created_at": "Wed Dec 11 15:58:22 +0000 2024", + "conversation_id_str": "1866875179789394001", + "display_text_range": [ + 0, + 36 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/KdDyEzbjZc", + "expanded_url": "https://x.com/killersawgaming/status/1866875179789394001/video/1", + "id_str": "1866875128404987904", + "indices": [ + 37, + 60 + ], + "media_key": "13_1866875128404987904", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1866875128404987904/img/FNRvR8kBRp9OI60A.jpg", + "type": "video", + "url": "https://t.co/KdDyEzbjZc", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "medium": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "small": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 576, + "width": 576, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 1, + 1 + ], + "duration_millis": 22710, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/pl/jKAUjZW8n_83LiCg.m3u8?tag=16&v=cfc" + }, + { + "bitrate": 432000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/vid/avc1/320x320/C6JTsmqYBILJget1.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/vid/avc1/576x576/eczAvtLqElGsviTl.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1866875128404987904" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/KdDyEzbjZc", + "expanded_url": "https://x.com/killersawgaming/status/1866875179789394001/video/1", + "id_str": "1866875128404987904", + "indices": [ + 37, + 60 + ], + "media_key": "13_1866875128404987904", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1866875128404987904/img/FNRvR8kBRp9OI60A.jpg", + "type": "video", + "url": "https://t.co/KdDyEzbjZc", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "medium": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "small": { + "h": 576, + "w": 576, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 576, + "width": 576, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 1, + 1 + ], + "duration_millis": 22710, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/pl/jKAUjZW8n_83LiCg.m3u8?tag=16&v=cfc" + }, + { + "bitrate": 432000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/vid/avc1/320x320/C6JTsmqYBILJget1.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1866875128404987904/vid/avc1/576x576/eczAvtLqElGsviTl.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1866875128404987904" + } + } + } + ] + }, + "favorite_count": 73586, + "favorited": false, + "full_text": "I found the perfect counter for Jeff https://t.co/KdDyEzbjZc", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 399, + "reply_count": 381, + "retweet_count": 6591, + "retweeted": false, + "user_id_str": "871807426839928835", + "id_str": "1866875179789394001" + } + } + }, + "legacy": { + "bookmark_count": 6996, + "bookmarked": true, + "created_at": "Thu Dec 12 02:10:45 +0000 2024", + "conversation_id_str": "1867029292565090679", + "display_text_range": [ + 0, + 106 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 143499, + "favorited": false, + "full_text": "I haven’t played marvel rivals yet wtf do people have against the cute shark puppy what the hell did he do", + "is_quote_status": true, + "lang": "en", + "quote_count": 289, + "quoted_status_id_str": "1866875179789394001", + "quoted_status_permalink": { + "url": "https://t.co/grdEkhtVh0", + "expanded": "https://twitter.com/killersawgaming/status/1866875179789394001", + "display": "x.com/killersawgamin…" + }, + "reply_count": 1675, + "retweet_count": 4636, + "retweeted": false, + "user_id_str": "1047122189684592640", + "id_str": "1867029292565090679" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1866624397521346733", + "sortIndex": "1818266991818461492", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1866624397521346733", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoyOTMyMzQ2NzEx", + "rest_id": "2932346711", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": false, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Fri Dec 19 17:50:29 +0000 2014", + "default_profile": true, + "default_profile_image": false, + "description": "Georgia bulldogs content creator. 24/7 Georgia , Atlanta falcon fan ! where I’m from we hate bama and Florida fans . go dawgs !!!", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 32292, + "followers_count": 8439, + "friends_count": 4980, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 29, + "location": "Athens, GA", + "media_count": 2567, + "name": "Coach T", + "normal_followers_count": 8439, + "pinned_tweet_ids_str": [ + "1866624397521346733" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/2932346711/1678633262", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1868138930907824128/6ziDpexE_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "TheRealRashaud", + "statuses_count": 19299, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1866624397521346733" + ], + "editable_until_msecs": "1733876511000", + "is_edit_eligible": false, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "9979470", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 8946, + "bookmarked": true, + "created_at": "Tue Dec 10 23:21:51 +0000 2024", + "conversation_id_str": "1866624397521346733", + "display_text_range": [ + 0, + 35 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/Wob2xlJCV2", + "expanded_url": "https://x.com/TheRealRashaud/status/1866624397521346733/photo/1", + "id_str": "1866624393704570880", + "indices": [ + 36, + 59 + ], + "media_key": "3_1866624393704570880", + "media_url_https": "https://pbs.twimg.com/media/GeeU_wvXAAAOn3F.jpg", + "type": "photo", + "url": "https://t.co/Wob2xlJCV2", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 870, + "y": 774, + "h": 110, + "w": 110 + } + ] + }, + "medium": { + "faces": [ + { + "x": 509, + "y": 453, + "h": 64, + "w": 64 + } + ] + }, + "small": { + "faces": [ + { + "x": 288, + "y": 256, + "h": 36, + "w": 36 + } + ] + }, + "orig": { + "faces": [ + { + "x": 870, + "y": 774, + "h": 110, + "w": 110 + } + ] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 0, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866624393704570880" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/Wob2xlJCV2", + "expanded_url": "https://x.com/TheRealRashaud/status/1866624397521346733/photo/1", + "id_str": "1866624393704570880", + "indices": [ + 36, + 59 + ], + "media_key": "3_1866624393704570880", + "media_url_https": "https://pbs.twimg.com/media/GeeU_wvXAAAOn3F.jpg", + "type": "photo", + "url": "https://t.co/Wob2xlJCV2", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 870, + "y": 774, + "h": 110, + "w": 110 + } + ] + }, + "medium": { + "faces": [ + { + "x": 509, + "y": 453, + "h": 64, + "w": 64 + } + ] + }, + "small": { + "faces": [ + { + "x": 288, + "y": 256, + "h": 36, + "w": 36 + } + ] + }, + "orig": { + "faces": [ + { + "x": 870, + "y": 774, + "h": 110, + "w": 110 + } + ] + } + }, + "sizes": { + "large": { + "h": 2048, + "w": 1536, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 900, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 510, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 2048, + "width": 1536, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1536, + "h": 860 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1536 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 1751 + }, + { + "x": 0, + "y": 0, + "w": 1024, + "h": 2048 + }, + { + "x": 0, + "y": 0, + "w": 1536, + "h": 2048 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1866624393704570880" + } + } + } + ] + }, + "favorite_count": 219607, + "favorited": false, + "full_text": "Wingstop done gave me a dickenstrip https://t.co/Wob2xlJCV2", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 1433, + "reply_count": 1222, + "retweet_count": 11876, + "retweeted": false, + "user_id_str": "2932346711", + "id_str": "1866624397521346733" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867089978267693408", + "sortIndex": "1818266981657320231", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867089978267693408", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0NDE5NjM5Nw==", + "rest_id": "44196397", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/X", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_bigger.jpg" + }, + "description": "X", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": false, + "can_media_tag": false, + "created_at": "Tue Jun 02 20:12:29 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "The people voted for major government reform", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 100977, + "followers_count": 207569546, + "friends_count": 873, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 155265, + "location": "", + "media_count": 2956, + "name": "Elon Musk", + "normal_followers_count": 207569546, + "pinned_tweet_ids_str": [ + "1868095929351368741" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/44196397/1726163678", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1858316737780781056/kPL61o0F_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "elonmusk", + "statuses_count": 62659, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1679729435447275522", + "professional_type": "Creator", + "category": [] + }, + "tipjar_settings": { + "is_enabled": false + }, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867089978267693408" + ], + "editable_until_msecs": "1733987514000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "32110808", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1867087144424182178", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxODU2NzUxNzg3NjQ0MjYwMzU0", + "rest_id": "1856751787644260354", + "affiliates_highlighted_label": {}, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Square", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Wed Nov 13 17:31:44 +0000 2024", + "default_profile": true, + "default_profile_image": false, + "description": "The people voted for major reform.", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 0, + "followers_count": 2387381, + "friends_count": 0, + "has_custom_timelines": false, + "is_translator": false, + "listed_count": 2904, + "location": "", + "media_count": 7, + "name": "Department of Government Efficiency", + "normal_followers_count": 2387381, + "pinned_tweet_ids_str": [], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1856751787644260354/1731523627", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1856763811186999296/p9VACiBj_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "DOGE", + "statuses_count": 22, + "translator_type": "none", + "verified": false, + "verified_type": "Government", + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867087144424182178" + ], + "editable_until_msecs": "1733986838000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "37036818", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 1786, + "bookmarked": false, + "created_at": "Thu Dec 12 06:00:38 +0000 2024", + "conversation_id_str": "1867087144424182178", + "display_text_range": [ + 0, + 267 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 26118, + "favorited": false, + "full_text": "DOGE is undergoing a serious analysis of wasteful and burdensome regulations, and is looking for public feedback! \n\nWhich are the really bad ones? Please DM us the CFR provision, the relevant text from the regulation, and the adverse consequences of said regulation.", + "is_quote_status": false, + "lang": "en", + "quote_count": 458, + "reply_count": 7804, + "retweet_count": 4645, + "retweeted": false, + "user_id_str": "1856751787644260354", + "id_str": "1867087144424182178" + } + } + }, + "legacy": { + "bookmark_count": 1896, + "bookmarked": true, + "created_at": "Thu Dec 12 06:11:54 +0000 2024", + "conversation_id_str": "1867089978267693408", + "display_text_range": [ + 0, + 75 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 71992, + "favorited": false, + "full_text": "Let us know about wasteful government spending and unnecessary regulations!", + "is_quote_status": true, + "lang": "en", + "quote_count": 785, + "quoted_status_id_str": "1867087144424182178", + "quoted_status_permalink": { + "url": "https://t.co/eOd7zJItVP", + "expanded": "https://twitter.com/doge/status/1867087144424182178", + "display": "x.com/doge/status/18…" + }, + "reply_count": 15121, + "retweet_count": 11117, + "retweeted": false, + "user_id_str": "44196397", + "id_str": "1867089978267693408" + }, + "trend_results": { + "rest_id": "1867741082479423540" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867074698598834440", + "sortIndex": "1818266977899331036", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867074698598834440", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0NDE5NjM5Nw==", + "rest_id": "44196397", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/X", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_bigger.jpg" + }, + "description": "X", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": false, + "can_media_tag": false, + "created_at": "Tue Jun 02 20:12:29 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "The people voted for major government reform", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 100977, + "followers_count": 207569546, + "friends_count": 873, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 155265, + "location": "", + "media_count": 2956, + "name": "Elon Musk", + "normal_followers_count": 207569546, + "pinned_tweet_ids_str": [ + "1868095929351368741" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/44196397/1726163678", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1858316737780781056/kPL61o0F_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "elonmusk", + "statuses_count": 62659, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1679729435447275522", + "professional_type": "Creator", + "category": [] + }, + "tipjar_settings": { + "is_enabled": false + }, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867074698598834440" + ], + "editable_until_msecs": "1733983871000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "25532134", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1867070557503754595", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoyOTI5MjkyNzE=", + "rest_id": "292929271", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/TPUSA", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1867642209253654528/K1HraQuc_bigger.jpg" + }, + "description": "Turning Point USA", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Wed May 04 13:37:25 +0000 2011", + "default_profile": true, + "default_profile_image": false, + "description": "Founder & CEO: @TPUSA + @TPAction_ • Host: The Charlie Kirk Show • Click the link below to subscribe \uD83C\uDDFA\uD83C\uDDF8", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "podcasts.apple.com/us/podcast/the…", + "expanded_url": "https://podcasts.apple.com/us/podcast/the-charlie-kirk-show/id1460600818", + "url": "https://t.co/qiKLOzdk76", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 35415, + "followers_count": 4317693, + "friends_count": 186496, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 9999, + "location": "Phoenix, AZ", + "media_count": 9725, + "name": "Charlie Kirk", + "normal_followers_count": 4317693, + "pinned_tweet_ids_str": [ + "1866994972349890632" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/292929271/1722636385", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1819144572179828740/z3ZuWW2J_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "charliekirk11", + "statuses_count": 66201, + "translator_type": "none", + "url": "https://t.co/qiKLOzdk76", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "tipjar_settings": {} + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867070557503754595" + ], + "editable_until_msecs": "1733982884000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "28345824", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 2662, + "bookmarked": false, + "created_at": "Thu Dec 12 04:54:44 +0000 2024", + "conversation_id_str": "1867070557503754595", + "display_text_range": [ + 0, + 252 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/0WVX7NdF9O", + "expanded_url": "https://x.com/bennyjohnson/status/1867069474181984545/video/1", + "id_str": "1867069412852838400", + "indices": [ + 229, + 252 + ], + "media_key": "13_1867069412852838400", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1867069412852838400/img/GIZThbEOC4v6W5k9.jpg", + "source_status_id_str": "1867069474181984545", + "source_user_id_str": "15212187", + "type": "video", + "url": "https://t.co/0WVX7NdF9O", + "additional_media_info": { + "monetizable": false, + "source_user": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxNTIxMjE4Nw==", + "rest_id": "15212187", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/TheBennyShowPod", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1696524738288615424/IveIyink_bigger.jpg" + }, + "description": "The Benny Show", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Mon Jun 23 21:33:20 +0000 2008", + "default_profile": false, + "default_profile_image": false, + "description": "i make internet", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "shop.bennyjohnson.com", + "expanded_url": "https://shop.bennyjohnson.com", + "url": "https://t.co/rtGD9ORLiB", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 23663, + "followers_count": 3232752, + "friends_count": 3544, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 8778, + "location": "Tampa Florida ", + "media_count": 36108, + "name": "Benny Johnson", + "normal_followers_count": 3232752, + "pinned_tweet_ids_str": [ + "1868295011252756587" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/15212187/1727468653", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1628474658583457793/KRPfh8Zb_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "bennyjohnson", + "statuses_count": 102574, + "translator_type": "none", + "url": "https://t.co/rtGD9ORLiB", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1494331868090494981", + "professional_type": "Creator", + "category": [ + { + "id": 933, + "name": "Media Personality", + "icon_name": "IconBriefcaseStroke" + } + ] + }, + "tipjar_settings": {}, + "super_follow_eligible": true + } + } + } + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 87820, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/pl/rIYg5s_e0Az8_79N.m3u8?tag=16" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/480x270/92Eniuw-vdZnWbU3.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/640x360/SUiDJKFXBOxFi9KB.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/1280x720/vQOxETxFkfKDqEOr.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1867069412852838400" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/0WVX7NdF9O", + "expanded_url": "https://x.com/bennyjohnson/status/1867069474181984545/video/1", + "id_str": "1867069412852838400", + "indices": [ + 229, + 252 + ], + "media_key": "13_1867069412852838400", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1867069412852838400/img/GIZThbEOC4v6W5k9.jpg", + "source_status_id_str": "1867069474181984545", + "source_user_id_str": "15212187", + "type": "video", + "url": "https://t.co/0WVX7NdF9O", + "additional_media_info": { + "monetizable": false, + "source_user": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxNTIxMjE4Nw==", + "rest_id": "15212187", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/TheBennyShowPod", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1696524738288615424/IveIyink_bigger.jpg" + }, + "description": "The Benny Show", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": true, + "created_at": "Mon Jun 23 21:33:20 +0000 2008", + "default_profile": false, + "default_profile_image": false, + "description": "i make internet", + "entities": { + "description": { + "urls": [] + }, + "url": { + "urls": [ + { + "display_url": "shop.bennyjohnson.com", + "expanded_url": "https://shop.bennyjohnson.com", + "url": "https://t.co/rtGD9ORLiB", + "indices": [ + 0, + 23 + ] + } + ] + } + }, + "fast_followers_count": 0, + "favourites_count": 23663, + "followers_count": 3232752, + "friends_count": 3544, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 8778, + "location": "Tampa Florida ", + "media_count": 36108, + "name": "Benny Johnson", + "normal_followers_count": 3232752, + "pinned_tweet_ids_str": [ + "1868295011252756587" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/15212187/1727468653", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1628474658583457793/KRPfh8Zb_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "bennyjohnson", + "statuses_count": 102574, + "translator_type": "none", + "url": "https://t.co/rtGD9ORLiB", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1494331868090494981", + "professional_type": "Creator", + "category": [ + { + "id": 933, + "name": "Media Personality", + "icon_name": "IconBriefcaseStroke" + } + ] + }, + "tipjar_settings": {}, + "super_follow_eligible": true + } + } + } + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 87820, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/pl/rIYg5s_e0Az8_79N.m3u8?tag=16" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/480x270/92Eniuw-vdZnWbU3.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/640x360/SUiDJKFXBOxFi9KB.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1867069412852838400/vid/avc1/1280x720/vQOxETxFkfKDqEOr.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1867069412852838400" + } + } + } + ] + }, + "favorite_count": 33889, + "favorited": false, + "full_text": "The Trump Assassination Task Force reveals that the Secret Service Agent who spotted Ryan Routh in the trees at the golf course, opened fire on the suspect and…\n\nMISSED HIM SIX TIMES FROM FIVE FEET AWAY!! \n\nHow is that possible? https://t.co/0WVX7NdF9O", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 1032, + "reply_count": 3833, + "retweet_count": 9475, + "retweeted": false, + "user_id_str": "292929271", + "id_str": "1867070557503754595" + } + } + }, + "legacy": { + "bookmark_count": 4267, + "bookmarked": true, + "created_at": "Thu Dec 12 05:11:11 +0000 2024", + "conversation_id_str": "1867074698598834440", + "display_text_range": [ + 0, + 1 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 111592, + "favorited": false, + "full_text": "\uD83E\uDD28", + "is_quote_status": true, + "lang": "art", + "quote_count": 772, + "quoted_status_id_str": "1867070557503754595", + "quoted_status_permalink": { + "url": "https://t.co/NasWgxdtvd", + "expanded": "https://twitter.com/charliekirk11/status/1867070557503754595", + "display": "x.com/charliekirk11/…" + }, + "reply_count": 5700, + "retweet_count": 25606, + "retweeted": false, + "user_id_str": "44196397", + "id_str": "1867074698598834440" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "tweet-1867079795382550829", + "sortIndex": "1818266962634755731", + "content": { + "entryType": "TimelineTimelineItem", + "__typename": "TimelineTimelineItem", + "itemContent": { + "itemType": "TimelineTweet", + "__typename": "TimelineTweet", + "tweet_results": { + "result": { + "__typename": "Tweet", + "rest_id": "1867079795382550829", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjo0NDE5NjM5Nw==", + "rest_id": "44196397", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/X", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1683899100922511378/5lY42eHs_bigger.jpg" + }, + "description": "X", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": false, + "can_media_tag": false, + "created_at": "Tue Jun 02 20:12:29 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "The people voted for major government reform", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 100977, + "followers_count": 207569546, + "friends_count": 873, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 155265, + "location": "", + "media_count": 2956, + "name": "Elon Musk", + "normal_followers_count": 207569546, + "pinned_tweet_ids_str": [ + "1868095929351368741" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/44196397/1726163678", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1858316737780781056/kPL61o0F_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "elonmusk", + "statuses_count": 62659, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1679729435447275522", + "professional_type": "Creator", + "category": [] + }, + "tipjar_settings": { + "is_enabled": false + }, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867079795382550829" + ], + "editable_until_msecs": "1733985086000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "9033801", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "quoted_status_result": { + "result": { + "__typename": "Tweet", + "rest_id": "1867069467152253406", + "core": { + "user_results": { + "result": { + "__typename": "User", + "id": "VXNlcjoxMzg5OTEzNTY3NjcxOTc1OTM3", + "rest_id": "1389913567671975937", + "affiliates_highlighted_label": { + "label": { + "url": { + "url": "https://twitter.com/MyDoge", + "urlType": "DeepLink" + }, + "badge": { + "url": "https://pbs.twimg.com/profile_images/1656530956801523719/SC7nEKMa_bigger.png" + }, + "description": "MyDoge", + "userLabelType": "BusinessLabel", + "userLabelDisplayType": "Badge" + } + }, + "has_graduated_access": true, + "is_blue_verified": true, + "profile_image_shape": "Circle", + "legacy": { + "following": false, + "can_dm": true, + "can_media_tag": false, + "created_at": "Wed May 05 12:03:43 +0000 2021", + "default_profile": true, + "default_profile_image": false, + "description": "UX/UI & Graphic Designer at Dogecoin MyDoge DogeOS / \uD835\uDD4F Creator", + "entities": { + "description": { + "urls": [] + } + }, + "fast_followers_count": 0, + "favourites_count": 95529, + "followers_count": 1124725, + "friends_count": 998, + "has_custom_timelines": true, + "is_translator": false, + "listed_count": 3716, + "location": "", + "media_count": 21320, + "name": "DogeDesigner", + "normal_followers_count": 1124725, + "pinned_tweet_ids_str": [ + "1821687464974860662" + ], + "possibly_sensitive": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1389913567671975937/1711058670", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1498070100393754625/C2V-fbll_normal.jpg", + "profile_interstitial_type": "", + "screen_name": "cb_doge", + "statuses_count": 41374, + "translator_type": "none", + "verified": false, + "want_retweets": false, + "withheld_in_countries": [] + }, + "professional": { + "rest_id": "1463329767675944960", + "professional_type": "Creator", + "category": [] + }, + "tipjar_settings": { + "is_enabled": true + }, + "super_follow_eligible": true + } + } + }, + "unmention_data": {}, + "edit_control": { + "edit_tweet_ids": [ + "1867069467152253406" + ], + "editable_until_msecs": "1733982624000", + "is_edit_eligible": true, + "edits_remaining": "5" + }, + "is_translatable": false, + "views": { + "count": "9088399", + "state": "EnabledWithCount" + }, + "source": "Twitter for iPhone", + "legacy": { + "bookmark_count": 82, + "bookmarked": false, + "created_at": "Thu Dec 12 04:50:24 +0000 2024", + "conversation_id_str": "1867069467152253406", + "display_text_range": [ + 0, + 22 + ], + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/YBfVzPmOU3", + "expanded_url": "https://x.com/cb_doge/status/1867069467152253406/photo/1", + "id_str": "1867069444347822080", + "indices": [ + 23, + 46 + ], + "media_key": "3_1867069444347822080", + "media_url_https": "https://pbs.twimg.com/media/GekpxHlWgAALLg4.jpg", + "type": "photo", + "url": "https://t.co/YBfVzPmOU3", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1010, + "w": 800, + "resize": "fit" + }, + "medium": { + "h": 1010, + "w": 800, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 539, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1010, + "width": 800, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 800, + "h": 448 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 800 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 912 + }, + { + "x": 177, + "y": 0, + "w": 505, + "h": 1010 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 1010 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867069444347822080" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "extended_entities": { + "media": [ + { + "display_url": "pic.x.com/YBfVzPmOU3", + "expanded_url": "https://x.com/cb_doge/status/1867069467152253406/photo/1", + "id_str": "1867069444347822080", + "indices": [ + 23, + 46 + ], + "media_key": "3_1867069444347822080", + "media_url_https": "https://pbs.twimg.com/media/GekpxHlWgAALLg4.jpg", + "type": "photo", + "url": "https://t.co/YBfVzPmOU3", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1010, + "w": 800, + "resize": "fit" + }, + "medium": { + "h": 1010, + "w": 800, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 539, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1010, + "width": 800, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 800, + "h": 448 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 800 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 912 + }, + { + "x": 177, + "y": 0, + "w": 505, + "h": 1010 + }, + { + "x": 0, + "y": 0, + "w": 800, + "h": 1010 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1867069444347822080" + } + } + } + ] + }, + "favorite_count": 1833, + "favorited": false, + "full_text": "Media Viewership vs. \uD835\uDD4F https://t.co/YBfVzPmOU3", + "is_quote_status": false, + "lang": "en", + "possibly_sensitive": false, + "possibly_sensitive_editable": true, + "quote_count": 94, + "reply_count": 423, + "retweet_count": 354, + "retweeted": false, + "user_id_str": "1389913567671975937", + "id_str": "1867069467152253406" + } + } + }, + "legacy": { + "bookmark_count": 713, + "bookmarked": true, + "created_at": "Thu Dec 12 05:31:26 +0000 2024", + "conversation_id_str": "1867079795382550829", + "display_text_range": [ + 0, + 43 + ], + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 58873, + "favorited": false, + "full_text": "Legacy media viewership drops while \uD835\uDD4F rises", + "is_quote_status": true, + "lang": "en", + "quote_count": 357, + "quoted_status_id_str": "1867069467152253406", + "quoted_status_permalink": { + "url": "https://t.co/2BpoMsDmdH", + "expanded": "https://twitter.com/cb_doge/status/1867069467152253406", + "display": "x.com/cb_doge/status…" + }, + "reply_count": 3897, + "retweet_count": 8788, + "retweeted": false, + "user_id_str": "44196397", + "id_str": "1867079795382550829" + } + } + }, + "tweetDisplayType": "Tweet" + } + } + }, + { + "entryId": "cursor-top-1818267260519218620", + "sortIndex": "1818267260519218620", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "HCb29sOfk5zkuzIAAA==", + "cursorType": "Top" + } + }, + { + "entryId": "cursor-bottom-1818266962634755730", + "sortIndex": "1818266962634755730", + "content": { + "entryType": "TimelineTimelineCursor", + "__typename": "TimelineTimelineCursor", + "value": "HBamuuLq54rkuzIAAA==", + "cursorType": "Bottom", + "stopOnEmptyResponse": true + } + } + ] + } + ], + "responseObjects": { + "feedbackActions": [], + "immediateReactions": [] + } + } + } + } +} \ No newline at end of file From a82a868bbb9117d2adc7f06ee5c05a75b06128c3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 15:21:02 -0800 Subject: [PATCH 04/25] Refactor runJobIndexBookmarks to work for bookmarks instead of likes, but I still need to implement the IPC functions --- src/renderer/src/automation_errors.ts | 10 +++++ .../src/view_models/AccountXViewModel.ts | 43 ++++++++++--------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/renderer/src/automation_errors.ts b/src/renderer/src/automation_errors.ts index c32cbc3c..6e20a172 100644 --- a/src/renderer/src/automation_errors.ts +++ b/src/renderer/src/automation_errors.ts @@ -26,6 +26,11 @@ export enum AutomationErrorType { x_runJob_indexMessages_URLChangedButDidnt = "x_runJob_indexMessages_URLChangedButDidnt", x_runJob_indexMessages_OtherError = "x_runJob_indexMessages_OtherError", x_runJob_indexMessages_ParseMessagesError = "x_runJob_indexMessages_ParseMessagesError", + x_runJob_indexBookmarks_Timeout = "x_runJob_indexBookmarks_Timeout", + x_runJob_indexBookmarks_URLChanged = "x_runJob_indexBookmarks_URLChanged", + x_runJob_indexBookmarks_OtherError = "x_runJob_indexBookmarks_OtherError", + x_runJob_indexBookmarks_ParseTweetsError = "x_runJob_indexBookmarks_ParseTweetsError", + x_runJob_indexBookmarks_VerifyThereIsNoMoreError = "x_runJob_indexBookmarks_VerifyThereIsNoMoreError", x_runJob_archiveBuild_ArchiveBuildError = "x_runJob_archiveBuild_ArchiveBuildError", x_runJob_deleteTweets_FailedToStart = "x_runJob_deleteTweets_FailedToStart", x_runJob_deleteTweets_Ct0CookieNotFound = "x_runJob_deleteTweets_Ct0CookieNotFound", @@ -95,6 +100,11 @@ export const AutomationErrorTypeToMessage = { [AutomationErrorType.x_runJob_indexMessages_URLChangedButDidnt]: "URL changed (but didn't) while indexing conversations", [AutomationErrorType.x_runJob_indexMessages_OtherError]: "Error while indexing messages", [AutomationErrorType.x_runJob_indexMessages_ParseMessagesError]: "Failed to parse messages while indexing messages", + [AutomationErrorType.x_runJob_indexBookmarks_Timeout]: "Timeout while indexing bookmarks", + [AutomationErrorType.x_runJob_indexBookmarks_URLChanged]: "URL changed while indexing bookmarks", + [AutomationErrorType.x_runJob_indexBookmarks_OtherError]: "Error while indexing bookmarks", + [AutomationErrorType.x_runJob_indexBookmarks_ParseTweetsError]: "Failed to parse tweets while indexing bookmarks", + [AutomationErrorType.x_runJob_indexBookmarks_VerifyThereIsNoMoreError]: "Failed to verify you finished saving bookmarks while indexing bookmarks", [AutomationErrorType.x_runJob_archiveBuild_ArchiveBuildError]: "Failed to archive build", [AutomationErrorType.x_runJob_deleteTweets_FailedToStart]: "Failed to start deleting tweets", [AutomationErrorType.x_runJob_deleteTweets_Ct0CookieNotFound]: "ct0 cookie not found while deleting tweets", diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/AccountXViewModel.ts index 9bca53e2..22d97c70 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/AccountXViewModel.ts @@ -72,6 +72,7 @@ export enum RunJobsState { export enum FailureState { indexTweets_FailedToRetryAfterRateLimit = "indexTweets_FailedToRetryAfterRateLimit", indexLikes_FailedToRetryAfterRateLimit = "indexLikes_FailedToRetryAfterRateLimit", + indexBookmarks_FailedToRetryAfterRateLimit = "indexBookmarks_FailedToRetryAfterRateLimit", } export type XViewModelState = { @@ -1660,43 +1661,43 @@ Hang on while I scroll down to your earliest boomarks.`; await this.loadURLWithRateLimit("https://x.com/" + this.account?.xAccount?.username + "/i/bookmarks"); await this.sleep(500); - // Check if likes list is empty + // Check if bookmarks list is empty if (await this.doesSelectorExist('div[data-testid="emptyState"]')) { - this.log("runJobIndexLikes", "no likes found"); - this.progress.isIndexLikesFinished = true; - this.progress.likesIndexed = 0; + this.log("runJobIndexBookmarks", "no bookmarks found"); + this.progress.isIndexBookmarksFinished = true; + this.progress.bookmarksIndexed = 0; await this.syncProgress(); } else { - this.log("runJobIndexLikes", "did not find empty state"); + this.log("runJobIndexBookmarks", "did not find empty state"); } - if (!this.progress.isIndexLikesFinished) { - // Wait for tweets to appear + if (!this.progress.isIndexBookmarksFinished) { + // Wait for bookmarks to appear try { - await this.waitForSelector('article', "https://x.com/" + this.account?.xAccount?.username + "/likes"); + await this.waitForSelector('article', "https://x.com/" + this.account?.xAccount?.username + "/i/bookmarks"); } catch (e) { - this.log("runJobIndexLikes", ["selector never appeared", e]); + this.log("runJobIndexBookmarks", ["selector never appeared", e]); if (e instanceof TimeoutError) { // Were we rate limited? this.rateLimitInfo = await window.electron.X.isRateLimited(this.account?.id); if (this.rateLimitInfo.isRateLimited) { await this.waitForRateLimit(); } else { - // If the page isn't loading, we assume the user has no likes yet + // If the page isn't loading, we assume the user has no bookmarks yet await this.waitForLoadingToFinish(); - this.progress.isIndexLikesFinished = true; - this.progress.likesIndexed = 0; + this.progress.isIndexBookmarksFinished = true; + this.progress.bookmarksIndexed = 0; await this.syncProgress(); } } else if (e instanceof URLChangedError) { const newURL = this.webview?.getURL(); - await this.error(AutomationErrorType.x_runJob_indexLikes_URLChanged, { + await this.error(AutomationErrorType.x_runJob_indexBookmarks_URLChanged, { newURL: newURL, exception: (e as Error).toString() }) errorTriggered = true; } else { - await this.error(AutomationErrorType.x_runJob_indexLikes_OtherError, { + await this.error(AutomationErrorType.x_runJob_indexBookmarks_OtherError, { exception: (e as Error).toString() }) errorTriggered = true; @@ -1709,7 +1710,7 @@ Hang on while I scroll down to your earliest boomarks.`; return false; } - while (this.progress.isIndexLikesFinished === false) { + while (this.progress.isIndexBookmarksFinished === false) { await this.waitForPause(); // Scroll to bottom @@ -1722,7 +1723,7 @@ Hang on while I scroll down to your earliest boomarks.`; await this.waitForRateLimit(); if (!await this.indexTweetsHandleRateLimit()) { // On fail, update the failure state and move on - await window.electron.X.setConfig(this.account?.id, FailureState.indexLikes_FailedToRetryAfterRateLimit, "true"); + await window.electron.X.setConfig(this.account?.id, FailureState.indexBookmarks_FailedToRetryAfterRateLimit, "true"); break; } await this.sleep(500); @@ -1731,10 +1732,10 @@ Hang on while I scroll down to your earliest boomarks.`; // Parse so far try { - this.progress = await window.electron.X.indexParseLikes(this.account?.id); + this.progress = await window.electron.X.indexParseBookmarks(this.account?.id); } catch (e) { const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); - await this.error(AutomationErrorType.x_runJob_indexLikes_ParseTweetsError, { + await this.error(AutomationErrorType.x_runJob_indexBookmarks_ParseTweetsError, { exception: (e as Error).toString() }, { latestResponseData: latestResponseData @@ -1754,7 +1755,7 @@ Hang on while I scroll down to your earliest boomarks.`; verifyResult = await this.indexTweetsVerifyThereIsNoMore(); } catch (e) { const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); - await this.error(AutomationErrorType.x_runJob_indexLikes_VerifyThereIsNoMoreError, { + await this.error(AutomationErrorType.x_runJob_indexBookmarks_VerifyThereIsNoMoreError, { exception: (e as Error).toString() }, { latestResponseData: latestResponseData, @@ -1766,10 +1767,10 @@ Hang on while I scroll down to your earliest boomarks.`; // If we verified that there are no more tweets, we're done if (verifyResult) { - this.progress = await window.electron.X.indexLikesFinished(this.account?.id); + this.progress = await window.electron.X.indexBookmarksFinished(this.account?.id); // On success, set the failure state to false - await window.electron.X.setConfig(this.account?.id, FailureState.indexLikes_FailedToRetryAfterRateLimit, "false"); + await window.electron.X.setConfig(this.account?.id, FailureState.indexBookmarks_FailedToRetryAfterRateLimit, "false"); break; } From 9cfa6afc6699307981914736c30c0f23074e517a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 15:59:45 -0800 Subject: [PATCH 05/25] Make indexParseTweetsResponseData work with bookmark data too, and write a test to count bookmarks --- src/account_x.test.ts | 25 ++++++++++++++ src/account_x.ts | 70 +++++++++++++++++++++++++++++++++---- src/account_x_types.ts | 78 +++++++++++++++++++++++++----------------- 3 files changed, 135 insertions(+), 38 deletions(-) diff --git a/src/account_x.test.ts b/src/account_x.test.ts index 2354e2e4..5655d115 100644 --- a/src/account_x.test.ts +++ b/src/account_x.test.ts @@ -162,6 +162,18 @@ class MockMITMController implements IMITMController { }, ]; } + if (testdata == "indexBookmarks") { + this.responseData = [ + { + host: 'x.com', + url: '/i/api/graphql/Ds7FCVYEIivOKHsGcE84xQ/Bookmarks?variables=%7B%22count%22%3A20%2C%22includePromotedContent%22%3Atrue%7D&features=%7B%22graphql_timeline_v2_bookmark_timeline%22%3Atrue%2C%22profile_label_improvements_pcf_label_in_post_enabled%22%3Afalse%2C%22rweb_tipjar_consumption_enabled%22%3Atrue%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22premium_content_api_read_enabled%22%3Afalse%2C%22communities_web_enable_tweet_community_results_fetch%22%3Atrue%2C%22c9s_tweet_anatomy_moderator_badge_enabled%22%3Atrue%2C%22responsive_web_grok_analyze_button_fetch_trends_enabled%22%3Atrue%2C%22articles_preview_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Atrue%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22creator_subscriptions_quote_tweet_preview_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22rweb_video_timestamps_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D', + status: 200, + headers: {}, + body: fs.readFileSync(path.join(__dirname, '..', 'testdata', 'XAPIBookmarks.json'), 'utf8'), + processed: false + } + ]; + } } setAutomationErrorReportTestdata(filename: string) { const testData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'testdata', 'automation-errors', filename), 'utf8')); @@ -703,6 +715,19 @@ test('XAccountController.indexParseAllJSON() should return user stats', async () expect(account.likesCount).toBe(177); }) +test("XAccountController.indexParsedTweets() should index bookmarks", async () => { + mitmController.setTestdata("indexBookmarks"); + if (controller.account) { + controller.account.username = 'nexamind91325'; + } + + const progress: XProgress = await controller.indexParseTweets() + expect(progress.bookmarksIndexed).toBe(14); + + const rows: XTweetRow[] = database.exec(controller.db, "SELECT * FROM tweet WHERE isBookmarked=1", [], "all") as XTweetRow[]; + expect(rows.length).toBe(14); +}) + // Testing the X migrations test("test migration: 20241016_add_config", async () => { diff --git a/src/account_x.ts b/src/account_x.ts index 8dcc5426..7d331253 100644 --- a/src/account_x.ts +++ b/src/account_x.ts @@ -47,6 +47,8 @@ import { XAPILegacyUser, XAPILegacyTweet, XAPIData, + XAPIBookmarksData, + XAPITimeline, XAPIInboxTimeline, XAPIInboxInitialState, XAPIConversation, @@ -61,6 +63,8 @@ import { XArchiveLike, XArchiveLikeContainer, isXArchiveLikeContainer, + isXAPIBookmarksData, + isXAPIData, // XArchiveDMConversation, } from './account_x_types' import * as XArchiveTypes from '../archive-static-sites/x-archive/src/types'; @@ -96,6 +100,7 @@ export interface XTweetRow { retweetCount: number; isLiked: boolean; isRetweeted: boolean; + isBookmarked: boolean; text: string; path: string; addedToDatabaseAt: string; @@ -391,7 +396,15 @@ export class XAccountController { `DROP TABLE tweet;`, `ALTER TABLE tweet_new RENAME TO tweet;` ] - } + }, + // Add isBookmarked to the tweet table, and update isBookarked for all tweets + { + name: "20241127_add_isBookmarked", + sql: [ + `ALTER TABLE tweet ADD COLUMN isBookmarked BOOLEAN;`, + `UPDATE tweet SET isBookmarked = 0;` + ] + }, ]) log.info("XAccountController.initDB: database initialized"); } @@ -537,7 +550,7 @@ export class XAccountController { } // Add the tweet - exec(this.db, 'INSERT INTO tweet (username, tweetID, conversationID, createdAt, likeCount, quoteCount, replyCount, retweetCount, isLiked, isRetweeted, text, path, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ + exec(this.db, 'INSERT INTO tweet (username, tweetID, conversationID, createdAt, likeCount, quoteCount, replyCount, retweetCount, isLiked, isRetweeted, isBookmarked, text, path, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ userLegacy["screen_name"], tweetLegacy["id_str"], tweetLegacy["conversation_id_str"], @@ -548,13 +561,16 @@ export class XAccountController { tweetLegacy["retweet_count"], tweetLegacy["favorited"] ? 1 : 0, tweetLegacy["retweeted"] ? 1 : 0, + tweetLegacy["bookmarked"] ? 1 : 0, tweetLegacy["full_text"], `${userLegacy['screen_name']}/status/${tweetLegacy['id_str']}`, new Date(), ]); // Update progress - if (tweetLegacy["full_text"].startsWith("RT @")) { + if (tweetLegacy["bookmarked"]) { + this.progress.bookmarksIndexed++; + } else if (tweetLegacy["full_text"].startsWith("RT @")) { // console.log("DEBUG-### RETWEET: ", tweetLegacy["id_str"], userLegacy["screen_name"], tweetLegacy["full_text"]); this.progress.retweetsIndexed++; } @@ -590,13 +606,29 @@ export class XAccountController { // Process the next response if ( - (responseData.url.includes("/UserTweetsAndReplies?") || responseData.url.includes("/Likes?")) && + ( + // Tweets + responseData.url.includes("/UserTweetsAndReplies?") || + // Likes + responseData.url.includes("/Likes?") || + // Bookmarks + responseData.url.includes("/Bookmarks?")) && responseData.status == 200 ) { - const body: XAPIData = JSON.parse(responseData.body); + // For likes and tweets, body is XAPIData + // For bookmarks, body is XAPIBookmarksData + const body: XAPIData | XAPIBookmarksData = JSON.parse(responseData.body); + let timeline: XAPITimeline; + if (isXAPIBookmarksData(body)) { + timeline = (body as XAPIBookmarksData).data.bookmark_timeline_v2; + } else if (isXAPIData(body)) { + timeline = (body as XAPIData).data.user.result.timeline_v2; + } else { + throw new Error('Invalid response data'); + } // Loop through instructions - body.data.user.result.timeline_v2.timeline.instructions.forEach((instructions) => { + timeline.timeline.instructions.forEach((instructions) => { if (instructions["type"] != "TimelineAddEntries") { return; } @@ -723,6 +755,20 @@ export class XAccountController { return this.progress; } + // Parses the response data so far to index bookmarks that have been collected + // Returns the progress object + async indexParseBookmarks(): Promise { + await this.mitmController.clearProcessed(); + log.info(`XAccountController.indexParseBookmarks: parsing ${this.mitmController.responseData.length} responses`); + + for (let i = 0; i < this.mitmController.responseData.length; i++) { + // Parsing bookmarks uses indexParseTweetsResponseData too, since it's the same data + this.indexParseTweetsResponseData(i); + } + + return this.progress; + } + async getProfileImageDataURI(user: XAPIUser): Promise { if (!user.profile_image_url_https) { return ""; @@ -1871,7 +1917,7 @@ export class XAccountController { skipCount++; } else { // Import it - exec(this.db, 'INSERT INTO tweet (username, tweetID, createdAt, likeCount, retweetCount, isLiked, isRetweeted, text, path, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ + exec(this.db, 'INSERT INTO tweet (username, tweetID, createdAt, likeCount, retweetCount, isLiked, isRetweeted, isBookmarked, text, path, addedToDatabaseAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ username, tweet.id_str, new Date(tweet.created_at), @@ -1879,6 +1925,7 @@ export class XAccountController { tweet.retweet_count, tweet.favorited ? 1 : 0, tweet.retweeted ? 1 : 0, + 0, tweet.full_text, `${username}/status/${tweet.id_str}`, new Date(), @@ -2250,6 +2297,15 @@ export const defineIPCX = () => { } }); + ipcMain.handle('X:indexParseBookmarks', async (_, accountID: number): Promise => { + try { + const controller = getXAccountController(accountID); + return await controller.indexParseBookmarks(); + } catch (error) { + throw new Error(packageExceptionForReport(error as Error)); + } + }); + ipcMain.handle('X:indexTweetsFinished', async (_, accountID: number): Promise => { try { const controller = getXAccountController(accountID); diff --git a/src/account_x_types.ts b/src/account_x_types.ts index 63e941c3..5780166e 100644 --- a/src/account_x_types.ts +++ b/src/account_x_types.ts @@ -121,47 +121,63 @@ export interface XAPIItemContent { user_results?: any; } +export interface XAPITimeline { + timeline: { + instructions: { + type: string; // "TimelineClearCache", "TimelineAddEntries" + entries?: { + content: { + entryType: string; // "TimelineTimelineModule", "TimelineTimelineItem", "TimelineTimelineCursor" + __typename: string; + value?: string; + cursorType?: string; + displayType?: string; + // items is there when entryType is "TimelineTimelineModule" + items?: { + entryId: string; + item: { + itemContent: XAPIItemContent; + clientEventInfo: any; + }; + }[]; + // itemContent is there when entryType is "TimelineTimelineItem" + itemContent?: XAPIItemContent; + clientEventInfo?: any; + metadata?: any; + }; + entryId: string; + sortIndex: string; + }[]; + }[]; + metadata: any; + } +} + export interface XAPIData { data: { user: { result: { __typename: string; // "User" - timeline_v2: { - timeline: { - instructions: { - type: string; // "TimelineClearCache", "TimelineAddEntries" - entries?: { - content: { - entryType: string; // "TimelineTimelineModule", "TimelineTimelineItem", "TimelineTimelineCursor" - __typename: string; - value?: string; - cursorType?: string; - displayType?: string; - // items is there when entryType is "TimelineTimelineModule" - items?: { - entryId: string; - item: { - itemContent: XAPIItemContent; - clientEventInfo: any; - }; - }[]; - // itemContent is there when entryType is "TimelineTimelineItem" - itemContent?: XAPIItemContent; - clientEventInfo?: any; - metadata?: any; - }; - entryId: string; - sortIndex: string; - }[]; - }[]; - metadata: any; - } - } + timeline_v2: XAPITimeline; } } } } +export interface XAPIBookmarksData { + data: { + bookmark_timeline_v2: XAPITimeline; + } +} + +export function isXAPIBookmarksData(body: any): body is XAPIBookmarksData { + return body.data && body.data.bookmark_timeline_v2; +} + +export function isXAPIData(body: any): body is XAPIData { + return body.data && body.data.user && body.data.user.result && body.data.user.result.timeline_v2; +} + // Index direct messages export interface XAPIConversationParticipant { From 1280c28aee1a5c65e7f7e687bfadcbd01f805a45 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 16:06:35 -0800 Subject: [PATCH 06/25] Use indexParseTweets for tweets, likes, and bookmarks --- src/account_x.ts | 47 +------------------ src/preload.ts | 3 ++ src/renderer/src/main.ts | 1 - src/renderer/src/test_util.ts | 1 - .../src/view_models/AccountXViewModel.ts | 4 +- 5 files changed, 6 insertions(+), 50 deletions(-) diff --git a/src/account_x.ts b/src/account_x.ts index 7d331253..9d6c778c 100644 --- a/src/account_x.ts +++ b/src/account_x.ts @@ -730,6 +730,7 @@ export class XAccountController { // Parses the response data so far to index tweets that have been collected // Returns the progress object + // This works for tweets, likes, and bookmarks async indexParseTweets(): Promise { await this.mitmController.clearProcessed(); log.info(`XAccountController.indexParseTweets: parsing ${this.mitmController.responseData.length} responses`); @@ -741,34 +742,6 @@ export class XAccountController { return this.progress; } - // Parses the response data so far to index likes that have been collected - // Returns the progress object - async indexParseLikes(): Promise { - await this.mitmController.clearProcessed(); - log.info(`XAccountController.indexParseLikes: parsing ${this.mitmController.responseData.length} responses`); - - for (let i = 0; i < this.mitmController.responseData.length; i++) { - // Parsing likes uses indexParseTweetsResponseData too, since it's the same data - this.indexParseTweetsResponseData(i); - } - - return this.progress; - } - - // Parses the response data so far to index bookmarks that have been collected - // Returns the progress object - async indexParseBookmarks(): Promise { - await this.mitmController.clearProcessed(); - log.info(`XAccountController.indexParseBookmarks: parsing ${this.mitmController.responseData.length} responses`); - - for (let i = 0; i < this.mitmController.responseData.length; i++) { - // Parsing bookmarks uses indexParseTweetsResponseData too, since it's the same data - this.indexParseTweetsResponseData(i); - } - - return this.progress; - } - async getProfileImageDataURI(user: XAPIUser): Promise { if (!user.profile_image_url_https) { return ""; @@ -2288,24 +2261,6 @@ export const defineIPCX = () => { } }); - ipcMain.handle('X:indexParseLikes', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexParseLikes(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - - ipcMain.handle('X:indexParseBookmarks', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexParseBookmarks(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - ipcMain.handle('X:indexTweetsFinished', async (_, accountID: number): Promise => { try { const controller = getXAccountController(accountID); diff --git a/src/preload.ts b/src/preload.ts index f38c3638..2ad758e4 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -149,6 +149,9 @@ contextBridge.exposeInMainWorld('electron', { indexParseLikes: (accountID: number): Promise => { return ipcRenderer.invoke('X:indexParseLikes', accountID) }, + indexParseBookmarks: (accountID: number): Promise => { + return ipcRenderer.invoke('X:indexParseBookmarks', accountID) + }, indexParseConversations: (accountID: number): Promise => { return ipcRenderer.invoke('X:indexParseConversations', accountID) }, diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts index f8eac8f1..67f7f5fe 100644 --- a/src/renderer/src/main.ts +++ b/src/renderer/src/main.ts @@ -73,7 +73,6 @@ declare global { indexStop: (accountID: number) => void; indexParseAllJSON: (accountID: number) => Promise; indexParseTweets: (accountID: number) => Promise; - indexParseLikes: (accountID: number) => Promise; indexParseConversations: (accountID: number) => Promise; indexIsThereMore: (accountID: number) => Promise; resetThereIsMore: (accountID: number) => Promise; diff --git a/src/renderer/src/test_util.ts b/src/renderer/src/test_util.ts index 96647e0f..1167b079 100644 --- a/src/renderer/src/test_util.ts +++ b/src/renderer/src/test_util.ts @@ -46,7 +46,6 @@ export const stubElectron = () => { indexStop: cy.stub(), indexParseAllJSON: cy.stub(), indexParseTweets: cy.stub(), - indexParseLikes: cy.stub(), indexParseConversations: cy.stub(), indexIsThereMore: cy.stub(), resetThereIsMore: cy.stub(), diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/AccountXViewModel.ts index 22d97c70..057eee0d 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/AccountXViewModel.ts @@ -1566,7 +1566,7 @@ Hang on while I scroll down to your earliest likes.`; // Parse so far try { - this.progress = await window.electron.X.indexParseLikes(this.account?.id); + this.progress = await window.electron.X.indexParseTweets(this.account?.id); } catch (e) { const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); await this.error(AutomationErrorType.x_runJob_indexLikes_ParseTweetsError, { @@ -1732,7 +1732,7 @@ Hang on while I scroll down to your earliest boomarks.`; // Parse so far try { - this.progress = await window.electron.X.indexParseBookmarks(this.account?.id); + this.progress = await window.electron.X.indexParseTweets(this.account?.id); } catch (e) { const latestResponseData = await window.electron.X.getLatestResponseData(this.account?.id); await this.error(AutomationErrorType.x_runJob_indexBookmarks_ParseTweetsError, { From da16b1c389ce263786e33ac4c0d66f0c35810cd3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 16:13:09 -0800 Subject: [PATCH 07/25] Delete all the indexLikesFinished() and similar functions, because all they do is update the progress in the main process. Instead, just update the progress and use syncProgress --- src/account_x.ts | 60 ------------------- src/preload.ts | 12 ---- src/renderer/src/main.ts | 4 -- src/renderer/src/test_util.ts | 4 -- .../src/view_models/AccountXViewModel.ts | 12 ++-- 5 files changed, 8 insertions(+), 84 deletions(-) diff --git a/src/account_x.ts b/src/account_x.ts index 9d6c778c..b7c15352 100644 --- a/src/account_x.ts +++ b/src/account_x.ts @@ -1084,24 +1084,6 @@ export class XAccountController { return this.progress; } - async indexTweetsFinished(): Promise { - log.info('XAccountController.indexTweetsFinished'); - this.progress.isIndexTweetsFinished = true; - return this.progress; - } - - async indexConversationsFinished(): Promise { - log.info('XAccountController.indexConversationsFinished'); - this.progress.isIndexConversationsFinished = true; - return this.progress; - } - - async indexMessagesFinished(): Promise { - log.info('XAccountController.indexMessagesFinished'); - this.progress.isIndexMessagesFinished = true; - return this.progress; - } - // Set the conversation's shouldIndexMessages to false async indexConversationFinished(conversationID: string) { if (!this.db) { @@ -1111,12 +1093,6 @@ export class XAccountController { exec(this.db, 'UPDATE conversation SET shouldIndexMessages = ? WHERE conversationID = ?', [0, conversationID]); } - async indexLikesFinished(): Promise { - log.info('XAccountController.indexLikesFinished'); - this.progress.isIndexLikesFinished = true; - return this.progress; - } - // When you start archiving tweets you: // - Return the URLs path, output path, and all expected filenames async archiveTweetsStart(): Promise { @@ -2261,15 +2237,6 @@ export const defineIPCX = () => { } }); - ipcMain.handle('X:indexTweetsFinished', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexTweetsFinished(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - ipcMain.handle('X:indexParseConversations', async (_, accountID: number): Promise => { try { const controller = getXAccountController(accountID); @@ -2315,24 +2282,6 @@ export const defineIPCX = () => { } }); - ipcMain.handle('X:indexConversationsFinished', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexConversationsFinished(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - - ipcMain.handle('X:indexMessagesFinished', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexMessagesFinished(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - ipcMain.handle('X:indexConversationFinished', async (_, accountID: number, conversationID: string): Promise => { try { const controller = getXAccountController(accountID); @@ -2342,15 +2291,6 @@ export const defineIPCX = () => { } }); - ipcMain.handle('X:indexLikesFinished', async (_, accountID: number): Promise => { - try { - const controller = getXAccountController(accountID); - return await controller.indexLikesFinished(); - } catch (error) { - throw new Error(packageExceptionForReport(error as Error)); - } - }); - ipcMain.handle('X:archiveTweetsStart', async (_, accountID: number): Promise => { try { const controller = getXAccountController(accountID); diff --git a/src/preload.ts b/src/preload.ts index 2ad758e4..3ce4e85b 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -167,21 +167,9 @@ contextBridge.exposeInMainWorld('electron', { indexParseMessages: (accountID: number): Promise => { return ipcRenderer.invoke('X:indexParseMessages', accountID) }, - indexTweetsFinished: (accountID: number): Promise => { - return ipcRenderer.invoke('X:indexTweetsFinished', accountID) - }, - indexConversationsFinished: (accountID: number): Promise => { - return ipcRenderer.invoke('X:indexConversationsFinished', accountID) - }, - indexMessagesFinished: (accountID: number): Promise => { - return ipcRenderer.invoke('X:indexMessagesFinished', accountID) - }, indexConversationFinished: (accountID: number, conversationID: string): Promise => { return ipcRenderer.invoke('X:indexConversationFinished', accountID, conversationID) }, - indexLikesFinished: (accountID: number): Promise => { - return ipcRenderer.invoke('X:indexLikesFinished', accountID) - }, archiveTweetsStart: (accountID: number): Promise => { return ipcRenderer.invoke('X:archiveTweetsStart', accountID) }, diff --git a/src/renderer/src/main.ts b/src/renderer/src/main.ts index 67f7f5fe..3df993fd 100644 --- a/src/renderer/src/main.ts +++ b/src/renderer/src/main.ts @@ -78,11 +78,7 @@ declare global { resetThereIsMore: (accountID: number) => Promise; indexMessagesStart: (accountID: number) => Promise; indexParseMessages: (accountID: number) => Promise; - indexTweetsFinished: (accountID: number) => Promise; - indexConversationsFinished: (accountID: number) => Promise; - indexMessagesFinished: (accountID: number) => Promise; indexConversationFinished: (accountID: number, conversationID: string) => Promise; - indexLikesFinished: (accountID: number) => Promise; archiveTweetsStart: (accountID: number) => Promise; archiveTweetsOutputPath: (accountID: number) => Promise; archiveTweet: (accountID: number, tweetID: string) => Promise; diff --git a/src/renderer/src/test_util.ts b/src/renderer/src/test_util.ts index 1167b079..549e910c 100644 --- a/src/renderer/src/test_util.ts +++ b/src/renderer/src/test_util.ts @@ -51,11 +51,7 @@ export const stubElectron = () => { resetThereIsMore: cy.stub(), indexMessagesStart: cy.stub(), indexParseMessages: cy.stub(), - indexTweetsFinished: cy.stub(), - indexConversationsFinished: cy.stub(), - indexMessagesFinished: cy.stub(), indexConversationFinished: cy.stub(), - indexLikesFinished: cy.stub(), archiveTweetsStart: cy.stub(), archiveTweetsOutputPath: cy.stub(), archiveTweet: cy.stub(), diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/AccountXViewModel.ts index 057eee0d..8789aa4e 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/AccountXViewModel.ts @@ -1099,7 +1099,8 @@ Hang on while I scroll down to your earliest tweets.`; // If we verified that there are no more tweets, we're done if (verifyResult) { - this.progress = await window.electron.X.indexTweetsFinished(this.account?.id); + this.progress.isIndexTweetsFinished = true; + await this.syncProgress(); // On success, set the failure state to false await window.electron.X.setConfig(this.account?.id, FailureState.indexTweets_FailedToRetryAfterRateLimit, "false"); @@ -1281,7 +1282,8 @@ Hang on while I scroll down to your earliest direct message conversations...`; // Check if we're done if (!await window.electron.X.indexIsThereMore(this.account?.id)) { - this.progress = await window.electron.X.indexConversationsFinished(this.account?.id); + this.progress.isIndexConversationsFinished = true; + await this.syncProgress(); break; } else { if (!moreToScroll) { @@ -1601,7 +1603,8 @@ Hang on while I scroll down to your earliest likes.`; // If we verified that there are no more tweets, we're done if (verifyResult) { - this.progress = await window.electron.X.indexLikesFinished(this.account?.id); + this.progress.isIndexLikesFinished = true; + await this.syncProgress(); // On success, set the failure state to false await window.electron.X.setConfig(this.account?.id, FailureState.indexLikes_FailedToRetryAfterRateLimit, "false"); @@ -1767,7 +1770,8 @@ Hang on while I scroll down to your earliest boomarks.`; // If we verified that there are no more tweets, we're done if (verifyResult) { - this.progress = await window.electron.X.indexBookmarksFinished(this.account?.id); + this.progress.isIndexBookmarksFinished = true; + await this.syncProgress(); // On success, set the failure state to false await window.electron.X.setConfig(this.account?.id, FailureState.indexBookmarks_FailedToRetryAfterRateLimit, "false"); From a7cb2fba42fc70d838b81a8105a8275f5aa9a8c3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 16:18:53 -0800 Subject: [PATCH 08/25] Add save bookmarks to review page --- src/renderer/src/views/x/XWizardReviewPage.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/src/views/x/XWizardReviewPage.vue b/src/renderer/src/views/x/XWizardReviewPage.vue index 11a04004..0e8bb868 100644 --- a/src/renderer/src/views/x/XWizardReviewPage.vue +++ b/src/renderer/src/views/x/XWizardReviewPage.vue @@ -81,6 +81,9 @@ onMounted(async () => {
  • Save likes
  • +
  • + Save bookmarks +
  • Save direct messages
  • From 69402c1c9f43a05b7434ff7a71483f0232276f64 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 16 Dec 2024 16:29:12 -0800 Subject: [PATCH 09/25] Fix loading bookmarks URL, make status and progress work with bookmarks, , and fix buttons on build options and review pages. --- src/renderer/src/view_models/AccountXViewModel.ts | 6 +++--- .../src/views/x/XFinishedRunningJobsPage.vue | 4 ++++ src/renderer/src/views/x/XJobStatusComponent.vue | 1 + src/renderer/src/views/x/XProgressComponent.vue | 12 ++++++++++++ src/renderer/src/views/x/XWizardBuildOptionsPage.vue | 2 +- src/renderer/src/views/x/XWizardReviewPage.vue | 4 ++-- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/view_models/AccountXViewModel.ts b/src/renderer/src/view_models/AccountXViewModel.ts index 8789aa4e..1b62714b 100644 --- a/src/renderer/src/view_models/AccountXViewModel.ts +++ b/src/renderer/src/view_models/AccountXViewModel.ts @@ -99,7 +99,7 @@ export class AccountXViewModel extends BaseViewModel { // they delete, this lets them go back and change settings without starting over public isDeleteReviewActive: boolean = false; - // Debug variables + // Variables related to debugging public debugAutopauseEndOfStep: boolean = false; async init(webview: WebviewTag) { @@ -1661,7 +1661,7 @@ Hang on while I scroll down to your earliest boomarks.`; let errorTriggered = false; await this.waitForPause(); await window.electron.X.resetRateLimitInfo(this.account?.id); - await this.loadURLWithRateLimit("https://x.com/" + this.account?.xAccount?.username + "/i/bookmarks"); + await this.loadURLWithRateLimit("https://x.com/i/bookmarks"); await this.sleep(500); // Check if bookmarks list is empty @@ -1677,7 +1677,7 @@ Hang on while I scroll down to your earliest boomarks.`; if (!this.progress.isIndexBookmarksFinished) { // Wait for bookmarks to appear try { - await this.waitForSelector('article', "https://x.com/" + this.account?.xAccount?.username + "/i/bookmarks"); + await this.waitForSelector('article', "https://x.com/i/bookmarks"); } catch (e) { this.log("runJobIndexBookmarks", ["selector never appeared", e]); if (e instanceof TimeoutError) { diff --git a/src/renderer/src/views/x/XFinishedRunningJobsPage.vue b/src/renderer/src/views/x/XFinishedRunningJobsPage.vue index f3eecee1..98b1395b 100644 --- a/src/renderer/src/views/x/XFinishedRunningJobsPage.vue +++ b/src/renderer/src/views/x/XFinishedRunningJobsPage.vue @@ -111,6 +111,10 @@ onMounted(async () => { {{ model.progress.likesIndexed.toLocaleString() }} likes +
  • + + {{ model.progress.bookmarksIndexed.toLocaleString() }} bookmarks +
  • {{ model.progress.unknownIndexed.toLocaleString() }} other tweets diff --git a/src/renderer/src/views/x/XJobStatusComponent.vue b/src/renderer/src/views/x/XJobStatusComponent.vue index 8ee20bdb..591603f4 100644 --- a/src/renderer/src/views/x/XJobStatusComponent.vue +++ b/src/renderer/src/views/x/XJobStatusComponent.vue @@ -24,6 +24,7 @@ const getJobTypeText = (jobType: string) => { downloadArchive: 'Downloading archive', indexTweets: 'Saving tweets', indexLikes: 'Saving likes', + indexBookmarks: 'Saving bookmarks', indexConversations: 'Saving conversations', indexMessages: 'Saving messages', archiveTweets: 'Saving tweets HTML', diff --git a/src/renderer/src/views/x/XProgressComponent.vue b/src/renderer/src/views/x/XProgressComponent.vue index 194af8fa..7c9b7ba5 100644 --- a/src/renderer/src/views/x/XProgressComponent.vue +++ b/src/renderer/src/views/x/XProgressComponent.vue @@ -120,6 +120,18 @@ onUnmounted(() => {

    + + +