mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-10 21:10:54 +01:00
Run prettier
This commit is contained in:
parent
b3d90f2b80
commit
27ca3fdf00
10 changed files with 175 additions and 119 deletions
|
@ -23,6 +23,7 @@
|
||||||
## Written in TypeScript as a Cloudflare Worker to scale, packed with more features and [best-in-class user privacy 🔒](#built-with-privacy-in-mind).
|
## Written in TypeScript as a Cloudflare Worker to scale, packed with more features and [best-in-class user privacy 🔒](#built-with-privacy-in-mind).
|
||||||
|
|
||||||
### Add `fx` before your `twitter.com` link to make it `fxtwitter.com`, OR
|
### Add `fx` before your `twitter.com` link to make it `fxtwitter.com`, OR
|
||||||
|
|
||||||
### Change `x.com` to `fixupx.com` in your link
|
### Change `x.com` to `fixupx.com` in your link
|
||||||
|
|
||||||
### For Twitter links on Discord, send a Twitter link and type `s/e/p` to make `twittpr.com`.
|
### For Twitter links on Discord, send a Twitter link and type `s/e/p` to make `twittpr.com`.
|
||||||
|
|
|
@ -40,8 +40,7 @@ const populateTweetProperties = async (
|
||||||
id: apiUser.id,
|
id: apiUser.id,
|
||||||
name: apiUser.name,
|
name: apiUser.name,
|
||||||
screen_name: apiUser.screen_name,
|
screen_name: apiUser.screen_name,
|
||||||
avatar_url:
|
avatar_url: (apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
|
||||||
(apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
|
|
||||||
avatar_color: '0000FF' /* colorFromPalette(
|
avatar_color: '0000FF' /* colorFromPalette(
|
||||||
tweet.user?.profile_image_extensions_media_color?.palette || []
|
tweet.user?.profile_image_extensions_media_color?.palette || []
|
||||||
),*/,
|
),*/,
|
||||||
|
@ -71,7 +70,7 @@ const populateTweetProperties = async (
|
||||||
|
|
||||||
apiTweet.replying_to = tweet.legacy?.in_reply_to_screen_name || null;
|
apiTweet.replying_to = tweet.legacy?.in_reply_to_screen_name || null;
|
||||||
apiTweet.replying_to_status = tweet.legacy?.in_reply_to_status_id_str || null;
|
apiTweet.replying_to_status = tweet.legacy?.in_reply_to_status_id_str || null;
|
||||||
|
|
||||||
const mediaList = Array.from(
|
const mediaList = Array.from(
|
||||||
tweet.legacy.extended_entities?.media || tweet.legacy.entities?.media || []
|
tweet.legacy.extended_entities?.media || tweet.legacy.entities?.media || []
|
||||||
);
|
);
|
||||||
|
@ -107,9 +106,14 @@ const populateTweetProperties = async (
|
||||||
console.log('note_tweet', JSON.stringify(tweet.note_tweet));
|
console.log('note_tweet', JSON.stringify(tweet.note_tweet));
|
||||||
const noteTweetText = tweet.note_tweet?.note_tweet_results?.result?.text;
|
const noteTweetText = tweet.note_tweet?.note_tweet_results?.result?.text;
|
||||||
/* For now, don't include note tweets */
|
/* For now, don't include note tweets */
|
||||||
if (noteTweetText && mediaList.length <= 0 && tweet.legacy.entities?.urls?.length <= 0) {
|
if (
|
||||||
|
noteTweetText /*&& mediaList.length <= 0 && tweet.legacy.entities?.urls?.length <= 0*/
|
||||||
|
) {
|
||||||
console.log('We meet the conditions to use new note tweets');
|
console.log('We meet the conditions to use new note tweets');
|
||||||
apiTweet.text = unescapeText(noteTweetText);
|
apiTweet.text = unescapeText(noteTweetText);
|
||||||
|
apiTweet.is_note_tweet = true;
|
||||||
|
} else {
|
||||||
|
apiTweet.is_note_tweet = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle photos and mosaic if available */
|
/* Handle photos and mosaic if available */
|
||||||
|
@ -129,7 +133,7 @@ const populateTweetProperties = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Populate a Twitter card */
|
/* Populate a Twitter card */
|
||||||
|
|
||||||
if (tweet.card) {
|
if (tweet.card) {
|
||||||
const card = renderCard(tweet.card);
|
const card = renderCard(tweet.card);
|
||||||
if (card.external_media) {
|
if (card.external_media) {
|
||||||
|
@ -143,7 +147,11 @@ const populateTweetProperties = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If a language is specified in API or by user, let's try translating it! */
|
/* If a language is specified in API or by user, let's try translating it! */
|
||||||
if (typeof language === 'string' && language.length === 2 && language !== tweet.legacy.lang) {
|
if (
|
||||||
|
typeof language === 'string' &&
|
||||||
|
language.length === 2 &&
|
||||||
|
language !== tweet.legacy.lang
|
||||||
|
) {
|
||||||
const translateAPI = await translateTweet(
|
const translateAPI = await translateTweet(
|
||||||
tweet,
|
tweet,
|
||||||
conversation.guestToken || '',
|
conversation.guestToken || '',
|
||||||
|
@ -213,15 +221,15 @@ export const statusAPI = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(JSON.stringify(tweet))
|
// console.log(JSON.stringify(tweet))
|
||||||
|
|
||||||
if (tweet.__typename === 'TweetUnavailable') {
|
if (tweet.__typename === 'TweetUnavailable') {
|
||||||
if (tweet.reason === 'Protected') {
|
if (tweet.reason === 'Protected') {
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags);
|
writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags);
|
||||||
return { code: 401, message: 'PRIVATE_TWEET' };
|
return { code: 401, message: 'PRIVATE_TWEET' };
|
||||||
// } else if (tweet.reason === 'NsfwLoggedOut') {
|
// } else if (tweet.reason === 'NsfwLoggedOut') {
|
||||||
// // API failure as elongator should have handled this
|
// // API failure as elongator should have handled this
|
||||||
// writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
// writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
||||||
// return { code: 500, message: 'API_FAIL' };
|
// return { code: 500, message: 'API_FAIL' };
|
||||||
} else {
|
} else {
|
||||||
// Api failure at parsing status
|
// Api failure at parsing status
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
||||||
|
@ -234,7 +242,7 @@ export const statusAPI = async (
|
||||||
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
|
||||||
return { code: 500, message: 'API_FAIL' };
|
return { code: 500, message: 'API_FAIL' };
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (tweet.retweeted_status_id_str) {
|
if (tweet.retweeted_status_id_str) {
|
||||||
tweet = conversation?.globalObjects?.tweets?.[tweet.retweeted_status_id_str] || {};
|
tweet = conversation?.globalObjects?.tweets?.[tweet.retweeted_status_id_str] || {};
|
||||||
|
|
52
src/fetch.ts
52
src/fetch.ts
|
@ -5,7 +5,7 @@ import { isGraphQLTweet } from './utils/graphql';
|
||||||
const API_ATTEMPTS = 3;
|
const API_ATTEMPTS = 3;
|
||||||
|
|
||||||
function generateCSRFToken() {
|
function generateCSRFToken() {
|
||||||
const randomBytes = new Uint8Array(160/2);
|
const randomBytes = new Uint8Array(160 / 2);
|
||||||
crypto.getRandomValues(randomBytes);
|
crypto.getRandomValues(randomBytes);
|
||||||
return Array.from(randomBytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
return Array.from(randomBytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ export const twitterFetch = async (
|
||||||
headers: headers
|
headers: headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await apiRequest?.json();
|
response = await apiRequest?.json();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
/* We'll usually only hit this if we get an invalid response from Twitter.
|
/* We'll usually only hit this if we get an invalid response from Twitter.
|
||||||
|
@ -197,28 +197,34 @@ export const fetchConversation = async (
|
||||||
`${
|
`${
|
||||||
Constants.TWITTER_ROOT
|
Constants.TWITTER_ROOT
|
||||||
}/i/api/graphql/2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId?variables=${encodeURIComponent(
|
}/i/api/graphql/2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId?variables=${encodeURIComponent(
|
||||||
JSON.stringify({"tweetId": status,"withCommunity":false,"includePromotedContent":false,"withVoice":false})
|
JSON.stringify({
|
||||||
|
tweetId: status,
|
||||||
|
withCommunity: false,
|
||||||
|
includePromotedContent: false,
|
||||||
|
withVoice: false
|
||||||
|
})
|
||||||
)}&features=${encodeURIComponent(
|
)}&features=${encodeURIComponent(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
creator_subscriptions_tweet_preview_api_enabled:true,
|
creator_subscriptions_tweet_preview_api_enabled: true,
|
||||||
tweetypie_unmention_optimization_enabled:true,
|
tweetypie_unmention_optimization_enabled: true,
|
||||||
responsive_web_edit_tweet_api_enabled:true,
|
responsive_web_edit_tweet_api_enabled: true,
|
||||||
graphql_is_translatable_rweb_tweet_is_translatable_enabled:true,
|
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
|
||||||
view_counts_everywhere_api_enabled:true,
|
view_counts_everywhere_api_enabled: true,
|
||||||
longform_notetweets_consumption_enabled:true,
|
longform_notetweets_consumption_enabled: true,
|
||||||
responsive_web_twitter_article_tweet_consumption_enabled:false,
|
responsive_web_twitter_article_tweet_consumption_enabled: false,
|
||||||
tweet_awards_web_tipping_enabled:false,
|
tweet_awards_web_tipping_enabled: false,
|
||||||
freedom_of_speech_not_reach_fetch_enabled:true,
|
freedom_of_speech_not_reach_fetch_enabled: true,
|
||||||
standardized_nudges_misinfo:true,
|
standardized_nudges_misinfo: true,
|
||||||
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled:true,
|
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
|
||||||
longform_notetweets_rich_text_read_enabled:true,
|
longform_notetweets_rich_text_read_enabled: true,
|
||||||
longform_notetweets_inline_media_enabled:true,
|
longform_notetweets_inline_media_enabled: true,
|
||||||
responsive_web_graphql_exclude_directive_enabled:true,
|
responsive_web_graphql_exclude_directive_enabled: true,
|
||||||
verified_phone_label_enabled:false,
|
verified_phone_label_enabled: false,
|
||||||
responsive_web_media_download_video_enabled:false,
|
responsive_web_media_download_video_enabled: false,
|
||||||
responsive_web_graphql_skip_user_profile_image_extensions_enabled:false,
|
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
|
||||||
responsive_web_graphql_timeline_navigation_enabled:true,
|
responsive_web_graphql_timeline_navigation_enabled: true,
|
||||||
responsive_web_enhance_cards_enabled:false})
|
responsive_web_enhance_cards_enabled: false
|
||||||
|
})
|
||||||
)}&fieldToggles=${encodeURIComponent(
|
)}&fieldToggles=${encodeURIComponent(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
// TODO Figure out what this property does
|
// TODO Figure out what this property does
|
||||||
|
@ -244,7 +250,7 @@ export const fetchConversation = async (
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Final clause for checking if it's valid is if there's errors
|
// Final clause for checking if it's valid is if there's errors
|
||||||
return Array.isArray(conversation.errors)
|
return Array.isArray(conversation.errors);
|
||||||
}
|
}
|
||||||
)) as TweetResultsByRestIdResult;
|
)) as TweetResultsByRestIdResult;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,10 @@ export const renderCard = (
|
||||||
): { poll?: APIPoll; external_media?: APIExternalMedia } => {
|
): { poll?: APIPoll; external_media?: APIExternalMedia } => {
|
||||||
// We convert the binding_values array into an object with the legacy format
|
// We convert the binding_values array into an object with the legacy format
|
||||||
// TODO Clean this up
|
// TODO Clean this up
|
||||||
const binding_values: Record<string, { string_value?: string; boolean_value?: boolean }> = {};
|
const binding_values: Record<
|
||||||
|
string,
|
||||||
|
{ string_value?: string; boolean_value?: boolean }
|
||||||
|
> = {};
|
||||||
if (Array.isArray(card.legacy.binding_values)) {
|
if (Array.isArray(card.legacy.binding_values)) {
|
||||||
card.legacy.binding_values.forEach(value => {
|
card.legacy.binding_values.forEach(value => {
|
||||||
if (value.key && value.value) {
|
if (value.key && value.value) {
|
||||||
|
@ -14,7 +17,6 @@ export const renderCard = (
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
console.log('rendering card');
|
console.log('rendering card');
|
||||||
|
|
||||||
|
@ -56,7 +58,10 @@ export const renderCard = (
|
||||||
});
|
});
|
||||||
|
|
||||||
return { poll: poll };
|
return { poll: poll };
|
||||||
} else if (typeof binding_values.player_url !== 'undefined' && binding_values.player_url.string_value) {
|
} else if (
|
||||||
|
typeof binding_values.player_url !== 'undefined' &&
|
||||||
|
binding_values.player_url.string_value
|
||||||
|
) {
|
||||||
/* Oh good, a non-Twitter video URL! This enables YouTube embeds and stuff to just work */
|
/* Oh good, a non-Twitter video URL! This enables YouTube embeds and stuff to just work */
|
||||||
return {
|
return {
|
||||||
external_media: {
|
external_media: {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/* Helps replace t.co links with their originals */
|
/* Helps replace t.co links with their originals */
|
||||||
export const linkFixer = (tweet: GraphQLTweet, text: string): string => {
|
export const linkFixer = (tweet: GraphQLTweet, text: string): string => {
|
||||||
console.log('got entites', {
|
console.log('got entites', {
|
||||||
entities: tweet.legacy.entities,
|
entities: tweet.legacy.entities
|
||||||
})
|
});
|
||||||
if (Array.isArray(tweet.legacy.entities?.urls) && tweet.legacy.entities.urls.length) {
|
if (Array.isArray(tweet.legacy.entities?.urls) && tweet.legacy.entities.urls.length) {
|
||||||
tweet.legacy.entities.urls.forEach((url: TcoExpansion) => {
|
tweet.legacy.entities.urls.forEach((url: TcoExpansion) => {
|
||||||
let newURL = url.expanded_url;
|
let newURL = url.expanded_url;
|
||||||
|
|
|
@ -313,7 +313,9 @@ router.get('/owoembed', async (request: IRequest) => {
|
||||||
provider_name:
|
provider_name:
|
||||||
searchParams.get('deprecated') === 'true'
|
searchParams.get('deprecated') === 'true'
|
||||||
? Strings.DEPRECATED_DOMAIN_NOTICE_DISCORD
|
? Strings.DEPRECATED_DOMAIN_NOTICE_DISCORD
|
||||||
: (useXbranding ? name : Strings.X_DOMAIN_NOTICE),
|
: useXbranding
|
||||||
|
? name
|
||||||
|
: Strings.X_DOMAIN_NOTICE,
|
||||||
provider_url: url,
|
provider_url: url,
|
||||||
title: Strings.DEFAULT_AUTHOR_TEXT,
|
title: Strings.DEFAULT_AUTHOR_TEXT,
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
|
|
@ -145,7 +145,8 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while
|
||||||
PLURAL_SECONDS_LEFT: 'seconds left',
|
PLURAL_SECONDS_LEFT: 'seconds left',
|
||||||
FINAL_POLL_RESULTS: 'Final results',
|
FINAL_POLL_RESULTS: 'Final results',
|
||||||
|
|
||||||
ERROR_API_FAIL: 'Tweet failed to load due to an API error. This is most common with NSFW Tweets as Twitter / X currently blocks us from fetching them. We\'re still working on a fix for that.🙏',
|
ERROR_API_FAIL:
|
||||||
|
"Tweet failed to load due to an API error. This is most common with NSFW Tweets as Twitter / X currently blocks us from fetching them. We're still working on a fix for that.🙏",
|
||||||
ERROR_PRIVATE: `Sorry, we can't embed this Tweet because the user is private or suspended :(`,
|
ERROR_PRIVATE: `Sorry, we can't embed this Tweet because the user is private or suspended :(`,
|
||||||
ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`,
|
ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`,
|
||||||
ERROR_USER_NOT_FOUND: `Sorry, that user doesn't exist :(`,
|
ERROR_USER_NOT_FOUND: `Sorry, that user doesn't exist :(`,
|
||||||
|
|
162
src/types/twitterTypes.d.ts
vendored
162
src/types/twitterTypes.d.ts
vendored
|
@ -310,19 +310,19 @@ type GraphQLTweet = {
|
||||||
result: GraphQLTweet;
|
result: GraphQLTweet;
|
||||||
__typename: 'Tweet';
|
__typename: 'Tweet';
|
||||||
rest_id: string; // "1674824189176590336",
|
rest_id: string; // "1674824189176590336",
|
||||||
has_birdwatch_notes: false,
|
has_birdwatch_notes: false;
|
||||||
core: {
|
core: {
|
||||||
user_results: {
|
user_results: {
|
||||||
result: GraphQLUser;
|
result: GraphQLUser;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
edit_control: unknown,
|
edit_control: unknown;
|
||||||
edit_perspective: unknown,
|
edit_perspective: unknown;
|
||||||
is_translatable: false,
|
is_translatable: false;
|
||||||
views: {
|
views: {
|
||||||
count: string; // "562"
|
count: string; // "562"
|
||||||
state: string; // "EnabledWithCount"
|
state: string; // "EnabledWithCount"
|
||||||
}
|
};
|
||||||
source: string; // "<a href=\"https://mobile.twitter.com\" rel=\"nofollow\">Twitter Web App</a>"
|
source: string; // "<a href=\"https://mobile.twitter.com\" rel=\"nofollow\">Twitter Web App</a>"
|
||||||
quoted_status_result?: GraphQLTweet;
|
quoted_status_result?: GraphQLTweet;
|
||||||
legacy: {
|
legacy: {
|
||||||
|
@ -356,45 +356,54 @@ type GraphQLTweet = {
|
||||||
indices: [number, number]; // [number, number]
|
indices: [number, number]; // [number, number]
|
||||||
media_url_https: string; // "https://pbs.twimg.com/media/FAKESCREENSHOT.jpg" With videos appears to be the thumbnail
|
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
|
type: string; // "photo" Seems to be photo even with videos
|
||||||
}[]
|
}[];
|
||||||
user_mentions: unknown[];
|
user_mentions: unknown[];
|
||||||
urls: TcoExpansion[];
|
urls: TcoExpansion[];
|
||||||
hashtags: unknown[];
|
hashtags: unknown[];
|
||||||
symbols: unknown[];
|
symbols: unknown[];
|
||||||
}
|
};
|
||||||
extended_entities: {
|
extended_entities: {
|
||||||
media: TweetMedia[]
|
media: TweetMedia[];
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
note_tweet: {
|
note_tweet: {
|
||||||
is_expandable: boolean;
|
is_expandable: boolean;
|
||||||
entity_set: {
|
entity_set: {
|
||||||
hashtags: unknown[];
|
hashtags: unknown[];
|
||||||
urls: unknown[];
|
urls: unknown[];
|
||||||
user_mentions: unknown[];
|
user_mentions: unknown[];
|
||||||
},
|
};
|
||||||
note_tweet_results: {
|
note_tweet_results: {
|
||||||
result: {
|
result: {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
card: {
|
card: {
|
||||||
rest_id: string; // "card://1674824189176590336",
|
rest_id: string; // "card://1674824189176590336",
|
||||||
legacy: {
|
legacy: {
|
||||||
binding_values: {
|
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'
|
key:
|
||||||
value: {
|
| `choice${1 | 2 | 3 | 4}_label`
|
||||||
string_value: string; // "Option text"
|
| 'counts_are_final'
|
||||||
type: 'STRING'
|
| `choice${1 | 2 | 3 | 4}_count`
|
||||||
}|{
|
| 'last_updated_datetime_utc'
|
||||||
boolean_value: boolean; // true
|
| 'duration_minutes'
|
||||||
type: 'BOOLEAN'
|
| 'api'
|
||||||
}
|
| 'card_url';
|
||||||
}[]
|
value:
|
||||||
}
|
| {
|
||||||
}
|
string_value: string; // "Option text"
|
||||||
}
|
type: 'STRING';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
boolean_value: boolean; // true
|
||||||
|
type: 'BOOLEAN';
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
type TweetTombstone = {
|
type TweetTombstone = {
|
||||||
__typename: 'TweetTombstone';
|
__typename: 'TweetTombstone';
|
||||||
tombstone: {
|
tombstone: {
|
||||||
|
@ -403,82 +412,91 @@ type TweetTombstone = {
|
||||||
rtl: boolean; // false;
|
rtl: boolean; // false;
|
||||||
text: string; // "You’re unable to view this Tweet because this account owner limits who can view their Tweets. Learn more"
|
text: string; // "You’re unable to view this Tweet because this account owner limits who can view their Tweets. Learn more"
|
||||||
entities: unknown[];
|
entities: unknown[];
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
type GraphQLTimelineTweetEntry = {
|
type GraphQLTimelineTweetEntry = {
|
||||||
/** The entryID contains the tweet ID */
|
/** The entryID contains the tweet ID */
|
||||||
entryId: `tweet-${number}`; // "tweet-1674824189176590336"
|
entryId: `tweet-${number}`; // "tweet-1674824189176590336"
|
||||||
sortIndex: string;
|
sortIndex: string;
|
||||||
content: {
|
content: {
|
||||||
entryType: 'TimelineTimelineItem',
|
entryType: 'TimelineTimelineItem';
|
||||||
__typename: 'TimelineTimelineItem',
|
__typename: 'TimelineTimelineItem';
|
||||||
itemContent: {
|
itemContent: {
|
||||||
item: 'TimelineTweet',
|
item: 'TimelineTweet';
|
||||||
__typename: 'TimelineTweet',
|
__typename: 'TimelineTweet';
|
||||||
tweet_results: {
|
tweet_results: {
|
||||||
result: GraphQLTweet|TweetTombstone;
|
result: GraphQLTweet | TweetTombstone;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
type GraphQLConversationThread = {
|
type GraphQLConversationThread = {
|
||||||
entryId: `conversationthread-${number}`; // "conversationthread-1674824189176590336"
|
entryId: `conversationthread-${number}`; // "conversationthread-1674824189176590336"
|
||||||
sortIndex: string;
|
sortIndex: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type GraphQLTimelineEntry = GraphQLTimelineTweetEntry|GraphQLConversationThread|unknown;
|
type GraphQLTimelineEntry =
|
||||||
|
| GraphQLTimelineTweetEntry
|
||||||
|
| GraphQLConversationThread
|
||||||
|
| unknown;
|
||||||
|
|
||||||
type V2ThreadInstruction = TimeLineAddEntriesInstruction | TimeLineTerminateTimelineInstruction;
|
type V2ThreadInstruction =
|
||||||
|
| TimeLineAddEntriesInstruction
|
||||||
|
| TimeLineTerminateTimelineInstruction;
|
||||||
|
|
||||||
type TimeLineAddEntriesInstruction = {
|
type TimeLineAddEntriesInstruction = {
|
||||||
type: 'TimelineAddEntries';
|
type: 'TimelineAddEntries';
|
||||||
entries: GraphQLTimelineEntry[];
|
entries: GraphQLTimelineEntry[];
|
||||||
}
|
};
|
||||||
|
|
||||||
type TimeLineTerminateTimelineInstruction = {
|
type TimeLineTerminateTimelineInstruction = {
|
||||||
type: 'TimelineTerminateTimeline';
|
type: 'TimelineTerminateTimeline';
|
||||||
direction: 'Top';
|
direction: 'Top';
|
||||||
}
|
};
|
||||||
type GraphQLTweetNotFoundResponse = {
|
type GraphQLTweetNotFoundResponse = {
|
||||||
errors: [{
|
errors: [
|
||||||
message: string; // "_Missing: No status found with that ID"
|
{
|
||||||
locations: unknown[];
|
message: string; // "_Missing: No status found with that ID"
|
||||||
path: string[]; // ["threaded_conversation_with_injections_v2"]
|
locations: unknown[];
|
||||||
extensions: {
|
path: string[]; // ["threaded_conversation_with_injections_v2"]
|
||||||
name: string; // "GenericError"
|
extensions: {
|
||||||
source: string; // "Server"
|
name: string; // "GenericError"
|
||||||
|
source: string; // "Server"
|
||||||
|
code: number; // 144
|
||||||
|
kind: string; // "NonFatal"
|
||||||
|
tracing: {
|
||||||
|
trace_id: string; // "2e39ff747de237db"
|
||||||
|
};
|
||||||
|
};
|
||||||
code: number; // 144
|
code: number; // 144
|
||||||
kind: string; // "NonFatal"
|
kind: string; // "NonFatal"
|
||||||
|
name: string; // "GenericError"
|
||||||
|
source: string; // "Server"
|
||||||
tracing: {
|
tracing: {
|
||||||
trace_id: string; // "2e39ff747de237db"
|
trace_id: string; // "2e39ff747de237db"
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
code: number; // 144
|
];
|
||||||
kind: string; // "NonFatal"
|
|
||||||
name: string; // "GenericError"
|
|
||||||
source: string; // "Server"
|
|
||||||
tracing: {
|
|
||||||
trace_id: string; // "2e39ff747de237db"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
data: Record<string, never>;
|
data: Record<string, never>;
|
||||||
}
|
};
|
||||||
type GraphQLTweetFoundResponse = {
|
type GraphQLTweetFoundResponse = {
|
||||||
data: {
|
data: {
|
||||||
threaded_conversation_with_injections_v2: {
|
threaded_conversation_with_injections_v2: {
|
||||||
instructions: V2ThreadInstruction[]
|
instructions: V2ThreadInstruction[];
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
type TweetResultsByRestIdResult = {
|
type TweetResultsByRestIdResult = {
|
||||||
errors?: unknown[];
|
errors?: unknown[];
|
||||||
data?: {
|
data?: {
|
||||||
tweetResult?: {
|
tweetResult?: {
|
||||||
result?: {
|
result?:
|
||||||
__typename: 'TweetUnavailable';
|
| {
|
||||||
reason: 'NsfwLoggedOut'|'Protected';
|
__typename: 'TweetUnavailable';
|
||||||
}|GraphQLTweet
|
reason: 'NsfwLoggedOut' | 'Protected';
|
||||||
}
|
}
|
||||||
}
|
| GraphQLTweet;
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
export const isGraphQLTweetNotFoundResponse = (response: unknown): response is GraphQLTweetNotFoundResponse => {
|
export const isGraphQLTweetNotFoundResponse = (
|
||||||
return typeof response === 'object' && response !== null && 'errors' in response && Array.isArray(response.errors) && response.errors.length > 0 && 'message' in response.errors[0] && response.errors[0].message === '_Missing: No status found with that ID';
|
response: unknown
|
||||||
|
): response is GraphQLTweetNotFoundResponse => {
|
||||||
|
return (
|
||||||
|
typeof response === 'object' &&
|
||||||
|
response !== null &&
|
||||||
|
'errors' in response &&
|
||||||
|
Array.isArray(response.errors) &&
|
||||||
|
response.errors.length > 0 &&
|
||||||
|
'message' in response.errors[0] &&
|
||||||
|
response.errors[0].message === '_Missing: No status found with that ID'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isGraphQLTweet = (response: unknown): response is GraphQLTweet => {
|
export const isGraphQLTweet = (response: unknown): response is GraphQLTweet => {
|
||||||
return typeof response === 'object' && response !== null && '__typename' in response && response.__typename === 'Tweet';
|
return (
|
||||||
}
|
typeof response === 'object' &&
|
||||||
|
response !== null &&
|
||||||
|
'__typename' in response &&
|
||||||
|
response.__typename === 'Tweet'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -284,4 +284,4 @@ test('API fetch user that does not exist', async () => {
|
||||||
expect(response.code).toEqual(404);
|
expect(response.code).toEqual(404);
|
||||||
expect(response.message).toEqual('User not found');
|
expect(response.message).toEqual('User not found');
|
||||||
expect(response.user).toBeUndefined();
|
expect(response.user).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue