/* Types for various Twitter API objects. Note that a lot of these are not actually complete types. Many unused values may be missing.*/ type TimelineContent = { item?: { content?: { tombstone?: { timestoneInfo: { richText: { text: string; }; }; }; }; }; }; type TimelineInstruction = { addEntries?: { entries: { content: TimelineContent; entryId: string; }[]; }; }; type TwitterAPIError = { code: number; message: string; }; type TimelineBlobPartial = { globalObjects: { tweets: { [tweetId: string]: TweetPartial; }; users: { [userId: string]: UserPartial; }; }; timeline: { instructions: TimelineInstruction[]; }; errors?: TwitterAPIError[]; guestToken?: string; }; type TweetMediaSize = { w: number; h: number; resize: 'crop' | 'fit'; }; type TweetMediaFormat = { bitrate: number; content_type: string; url: string; }; type TcoExpansion = { display_url: string; expanded_url: string; indices: [number, number]; url: string; }; type TweetMedia = { additional_media_info: { monetizable: boolean }; display_url: string; expanded_url: string; ext_media_color?: { palette?: MediaPlaceholderColor[]; }; ext_alt_text?: string; id_str: string; indices: [number, number]; media_key: string; media_url: string; media_url_https: string; original_info: { width: number; height: number }; sizes: { thumb: TweetMediaSize; large: TweetMediaSize; medium: TweetMediaSize; small: TweetMediaSize; }; type: 'photo' | 'video' | 'animated_gif'; url: string; video_info?: { aspect_ratio: [number, number]; duration_millis: number; variants: TweetMediaFormat[]; }; }; type CardValue = { type: 'BOOLEAN' | 'STRING'; boolean_value: boolean; string_value: string; }; type TweetCardBindingValues = { card_url: CardValue; choice1_count?: CardValue; choice2_count?: CardValue; choice3_count?: CardValue; choice4_count?: CardValue; choice1_label?: CardValue; choice2_label?: CardValue; choice3_label?: CardValue; choice4_label?: CardValue; counts_are_final?: CardValue; duration_minutes?: CardValue; end_datetime_utc?: CardValue; player_url?: CardValue; player_width?: CardValue; player_height?: CardValue; title?: CardValue; }; type TweetCard = { binding_values: TweetCardBindingValues; name: string; }; type TweetEntities = { urls?: TcoExpansion[]; media?: TweetMedia[]; }; /* TODO: Are there more states for ext_views? Legacy Tweets use Enabled but have no count, while newer tweets have EnabledWithCount and count is populated with a string. */ type ExtViews = { state: 'Enabled' | 'EnabledWithCount'; count?: string; }; type TweetPartial = { card?: TweetCard; conversation_id_str: string; created_at: string; // date string display_text_range: [number, number]; entities: TweetEntities; extended_entities: TweetEntities; ext_views?: ExtViews; favorite_count: number; in_reply_to_screen_name?: string; in_reply_to_status_id_str?: string; in_reply_to_user_id_str?: string; id_str: string; lang: string; possibly_sensitive: boolean; possibly_sensitive_editable: boolean; retweet_count: number; quote_count: number; quoted_status_id_str: string; reply_count: number; source: string; full_text: string; user_id_str: string; retweeted_status_id: number; retweeted_status_id_str: string; user?: UserPartial; }; type UserPartial = { id_str: string; name: string; screen_name: string; profile_image_url_https: string; profile_banner_url: string; profile_image_extensions_media_color?: { palette?: MediaPlaceholderColor[]; }; }; type MediaPlaceholderColor = { rgb: { red: number; green: number; blue: number; }; }; type TranslationPartial = { id_str: string; translationState: 'Success'; // TODO: figure out other values sourceLanguage: string; localizedSourceLanguage: string; destinationLanguage: string; translationSource: 'Google'; translation: string; entities: TweetEntities; }; type GraphQLUserResponse = { data: { user: { result: GraphQLUser; }; }; }; type GraphQLUser = { __typename: 'User'; id: string; // "VXNlcjo3ODMyMTQ=" rest_id: string; // "783214", affiliates_highlighted_label: { label?: { badge?: { url?: string; // "https://pbs.twimg.com/semantic_core_img/1290392753013002240/mWq1iE5L?format=png&name=orig" }; description?: string; // "United States government organization" url?: { url?: string; // "https://help.twitter.com/rules-and-policies/state-affiliated" urlType: string; // "DeepLink" }; }; }; business_account: { affiliates_count?: 20; }; is_blue_verified: boolean; // false, profile_image_shape: 'Circle' | 'Square' | 'Hexagon'; // "Circle", has_nft_avatar: boolean; // false, legacy: { created_at: string; // "Tue Feb 20 14:35:54 +0000 2007", default_profile: boolean; // false, default_profile_image: boolean; // false, description: string; // "What's happening?!", entities: { description?: { urls?: { display_url: string; // "about.twitter.com", expanded_url: string; // "https://about.twitter.com/", url: string; // "https://t.co/DAtOo6uuHk", indices: [0, 23]; }[]; }; url?: { urls?: { display_url: string; // "about.twitter.com", expanded_url: string; // "https://about.twitter.com/", url: string; // "https://t.co/DAtOo6uuHk", indices: [0, 23]; }[]; }; }; fast_followers_count: 0; favourites_count: number; // 126708, followers_count: number; // 4996, friends_count: number; // 2125, has_custom_timelines: boolean; // true, is_translator: boolean; // false, listed_count: number; // 88165, location: string; // "everywhere", media_count: number; // 20839, name: string; // "Twitter", normal_followers_count: number; // 65669107, pinned_tweet_ids_str: string[]; // Array of tweet ids, usually one. Empty if no pinned tweet possibly_sensitive: boolean; // false, profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/783214/1646075315", profile_image_url_https: string; // "https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_normal.jpg", profile_interstitial_type: string; // "", screen_name: string; // "Twitter", statuses_count: number; // 15047 translator_type: string; // "regular" verified: boolean; // false verified_type: 'Business' | 'Government'; withheld_in_countries: []; }; professional: { rest_id: string; // "1503055759638159366", professional_type: string; // "Creator", category: [ { id: number; // 354, name: string; // "Community", icon_name: string; // "IconBriefcaseStroke" } ]; }; legacy_extended_profile: { birthdate?: { day: number; // 7, month: number; // 1, visibility: string; // "Public" year: number; // 2000 year_visibility: string; // "Public" }; profile_image_shape: string; // "Circle", rest_id: string; // "783214", }; is_profile_translatable: false; verification_info: { reason: { description: { entities: { from_index: number; // 98, ref: { url: string; // "https://help.twitter.com/managing-your-account/about-twitter-verified-accounts", url_type: string; // "ExternalUrl" }; to_index: number; // 108 }[]; text?: | 'This account is verified because it’s subscribed to Twitter Blue or is a legacy verified account. Learn more' | "This account is verified because it's an official organisation on Twitter. Learn more"; }; }; }; }; type GraphQLTweetLegacy = { created_at: string; // "Tue Sep 14 20:00:00 +0000 2021" conversation_id_str: string; // "1674824189176590336" bookmark_count: number; // 0 bookmarked: boolean; // false favorite_count: number; // 28 full_text: string; // "This is a test tweet" in_reply_to_screen_name: string; // "username" in_reply_to_status_id_str: string; // "1674824189176590336" in_reply_to_user_id_str: string; // "783214" is_quote_status: boolean; // false quote_count: number; // 39 quoted_status_id_str: string; // "1674824189176590336" quoted_status_permalink: { url: string; // "https://t.co/aBcDeFgHiJ" expanded: string; // "https://twitter.com/username/status/1674824189176590336" display: string; // "twitter.com/username/statu…" }; reply_count: number; // 1 retweet_count: number; // 4 lang: string; // "en" possibly_sensitive: boolean; // false possibly_sensitive_editable: boolean; // false entities: { media: { display_url: string; // "pic.twitter.com/1X2X3X4X5X" expanded_url: string; // "https://twitter.com/username/status/1674824189176590336/photo/1" "https://twitter.com/username/status/1674824189176590336/video/1" id_str: string; // "1674824189176590336" indices: [number, number]; // [number, number] media_url_https: string; // "https://pbs.twimg.com/media/FAKESCREENSHOT.jpg" With videos appears to be the thumbnail type: string; // "photo" Seems to be photo even with videos }[]; user_mentions: unknown[]; urls: TcoExpansion[]; hashtags: unknown[]; symbols: unknown[]; }; extended_entities: { media: TweetMedia[]; }; }; type GraphQLTweet = { // Workaround result: GraphQLTweet; __typename: 'Tweet' | 'TweetUnavailable'; rest_id: string; // "1674824189176590336", has_birdwatch_notes: false; core: { user_results: { result: GraphQLUser; }; }; tweet?: { legacy: GraphQLTweetLegacy; views: { count: string; // "562" state: string; // "EnabledWithCount" }; core: { user_results: { result: GraphQLUser; }; }; }; edit_control: unknown; edit_perspective: unknown; is_translatable: false; views: { count: string; // "562" state: string; // "EnabledWithCount" }; source: string; // "Twitter Web App" quoted_status_result?: GraphQLTweet; legacy: GraphQLTweetLegacy; note_tweet: { is_expandable: boolean; note_tweet_results: { result: { entity_set: { hashtags: unknown[]; symbols: unknown[]; urls: TcoExpansion[]; user_mentions: unknown[]; }; media: { inline_media: unknown[]; }; richtext: { richtext_tags: { from_index: number; to_index: number; richtext_types: string[]; }[]; }; text: string; }; }; }; card: { rest_id: string; // "card://1674824189176590336", legacy: { binding_values: { key: | `choice${1 | 2 | 3 | 4}_label` | 'counts_are_final' | `choice${1 | 2 | 3 | 4}_count` | 'last_updated_datetime_utc' | 'duration_minutes' | 'api' | 'card_url'; value: | { string_value: string; // "Option text" type: 'STRING'; } | { boolean_value: boolean; // true type: 'BOOLEAN'; }; }[]; }; }; }; type TweetTombstone = { __typename: 'TweetTombstone'; tombstone: { __typename: 'TextTombstone'; text: { rtl: boolean; // false; text: string; // "You’re unable to view this Tweet because this account owner limits who can view their Tweets. Learn more" entities: unknown[]; }; }; }; type GraphQLTimelineTweetEntry = { /** The entryID contains the tweet ID */ entryId: `tweet-${number}`; // "tweet-1674824189176590336" sortIndex: string; content: { entryType: 'TimelineTimelineItem'; __typename: 'TimelineTimelineItem'; itemContent: { item: 'TimelineTweet'; __typename: 'TimelineTweet'; tweet_results: { result: GraphQLTweet | TweetTombstone; }; }; }; }; type GraphQLConversationThread = { entryId: `conversationthread-${number}`; // "conversationthread-1674824189176590336" sortIndex: string; }; type GraphQLTimelineEntry = | GraphQLTimelineTweetEntry | GraphQLConversationThread | unknown; type V2ThreadInstruction = | TimeLineAddEntriesInstruction | TimeLineTerminateTimelineInstruction; type TimeLineAddEntriesInstruction = { type: 'TimelineAddEntries'; entries: GraphQLTimelineEntry[]; }; type TimeLineTerminateTimelineInstruction = { type: 'TimelineTerminateTimeline'; direction: 'Top'; }; type GraphQLTweetNotFoundResponse = { errors: [ { message: string; // "_Missing: No status found with that ID" locations: unknown[]; path: string[]; // ["threaded_conversation_with_injections_v2"] extensions: { name: string; // "GenericError" source: string; // "Server" code: number; // 144 kind: string; // "NonFatal" tracing: { trace_id: string; // "2e39ff747de237db" }; }; code: number; // 144 kind: string; // "NonFatal" name: string; // "GenericError" source: string; // "Server" tracing: { trace_id: string; // "2e39ff747de237db" }; } ]; data: Record; }; type GraphQLTweetFoundResponse = { data: { threaded_conversation_with_injections_v2: { instructions: V2ThreadInstruction[]; }; }; }; type TweetResultsByRestIdResult = { guestToken?: string; errors?: unknown[]; data?: { tweetResult?: { result?: | { __typename: 'TweetUnavailable'; reason: 'NsfwLoggedOut' | 'Protected'; } | GraphQLTweet; }; }; };