Run prettier

This commit is contained in:
dangered wolf 2023-08-17 19:46:57 -04:00
parent b3d90f2b80
commit 27ca3fdf00
No known key found for this signature in database
GPG key ID: 41E4D37680ED8B58
10 changed files with 175 additions and 119 deletions

View file

@ -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`.

View file

@ -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] || {};

View file

@ -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;
}; };

View file

@ -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: {

View file

@ -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;

View file

@ -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',

View file

@ -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 :(`,

View file

@ -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; // "Youre unable to view this Tweet because this account owner limits who can view their Tweets. Learn more" text: string; // "Youre 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;
} };
};
};

View file

@ -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'
);
};

View file

@ -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();
}); });