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).
### Add `fx` before your `twitter.com` link to make it `fxtwitter.com`, OR
### 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`.

View file

@ -40,8 +40,7 @@ const populateTweetProperties = async (
id: apiUser.id,
name: apiUser.name,
screen_name: apiUser.screen_name,
avatar_url:
(apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
avatar_url: (apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
avatar_color: '0000FF' /* colorFromPalette(
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_status = tweet.legacy?.in_reply_to_status_id_str || null;
const mediaList = Array.from(
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));
const noteTweetText = tweet.note_tweet?.note_tweet_results?.result?.text;
/* 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');
apiTweet.text = unescapeText(noteTweetText);
apiTweet.is_note_tweet = true;
} else {
apiTweet.is_note_tweet = false;
}
/* Handle photos and mosaic if available */
@ -129,7 +133,7 @@ const populateTweetProperties = async (
}
/* Populate a Twitter card */
if (tweet.card) {
const card = renderCard(tweet.card);
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 (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(
tweet,
conversation.guestToken || '',
@ -213,15 +221,15 @@ export const statusAPI = async (
}
// console.log(JSON.stringify(tweet))
if (tweet.__typename === 'TweetUnavailable') {
if (tweet.reason === 'Protected') {
writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags);
return { code: 401, message: 'PRIVATE_TWEET' };
// } else if (tweet.reason === 'NsfwLoggedOut') {
// // API failure as elongator should have handled this
// writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
// return { code: 500, message: 'API_FAIL' };
// } else if (tweet.reason === 'NsfwLoggedOut') {
// // API failure as elongator should have handled this
// writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
// return { code: 500, message: 'API_FAIL' };
} else {
// Api failure at parsing status
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
@ -234,7 +242,7 @@ export const statusAPI = async (
writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
return { code: 500, message: 'API_FAIL' };
}
/*
if (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;
function generateCSRFToken() {
const randomBytes = new Uint8Array(160/2);
const randomBytes = new Uint8Array(160 / 2);
crypto.getRandomValues(randomBytes);
return Array.from(randomBytes, byte => byte.toString(16).padStart(2, '0')).join('');
}
@ -134,7 +134,7 @@ export const twitterFetch = async (
headers: headers
});
}
response = await apiRequest?.json();
} catch (e: unknown) {
/* 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
}/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(
JSON.stringify({
creator_subscriptions_tweet_preview_api_enabled:true,
tweetypie_unmention_optimization_enabled:true,
responsive_web_edit_tweet_api_enabled:true,
graphql_is_translatable_rweb_tweet_is_translatable_enabled:true,
view_counts_everywhere_api_enabled:true,
longform_notetweets_consumption_enabled:true,
responsive_web_twitter_article_tweet_consumption_enabled:false,
tweet_awards_web_tipping_enabled:false,
freedom_of_speech_not_reach_fetch_enabled:true,
standardized_nudges_misinfo:true,
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled:true,
longform_notetweets_rich_text_read_enabled:true,
longform_notetweets_inline_media_enabled:true,
responsive_web_graphql_exclude_directive_enabled:true,
verified_phone_label_enabled:false,
responsive_web_media_download_video_enabled:false,
responsive_web_graphql_skip_user_profile_image_extensions_enabled:false,
responsive_web_graphql_timeline_navigation_enabled:true,
responsive_web_enhance_cards_enabled:false})
creator_subscriptions_tweet_preview_api_enabled: true,
tweetypie_unmention_optimization_enabled: true,
responsive_web_edit_tweet_api_enabled: true,
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
view_counts_everywhere_api_enabled: true,
longform_notetweets_consumption_enabled: true,
responsive_web_twitter_article_tweet_consumption_enabled: false,
tweet_awards_web_tipping_enabled: false,
freedom_of_speech_not_reach_fetch_enabled: true,
standardized_nudges_misinfo: true,
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
longform_notetweets_rich_text_read_enabled: true,
longform_notetweets_inline_media_enabled: true,
responsive_web_graphql_exclude_directive_enabled: true,
verified_phone_label_enabled: false,
responsive_web_media_download_video_enabled: false,
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
responsive_web_graphql_timeline_navigation_enabled: true,
responsive_web_enhance_cards_enabled: false
})
)}&fieldToggles=${encodeURIComponent(
JSON.stringify({
// TODO Figure out what this property does
@ -244,7 +250,7 @@ export const fetchConversation = async (
return true;
}
// 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;
};

View file

@ -6,7 +6,10 @@ export const renderCard = (
): { poll?: APIPoll; external_media?: APIExternalMedia } => {
// We convert the binding_values array into an object with the legacy format
// 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)) {
card.legacy.binding_values.forEach(value => {
if (value.key && value.value) {
@ -14,7 +17,6 @@ export const renderCard = (
}
});
}
console.log('rendering card');
@ -56,7 +58,10 @@ export const renderCard = (
});
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 */
return {
external_media: {

View file

@ -1,8 +1,8 @@
/* Helps replace t.co links with their originals */
export const linkFixer = (tweet: GraphQLTweet, text: string): string => {
console.log('got entites', {
entities: tweet.legacy.entities,
})
entities: tweet.legacy.entities
});
if (Array.isArray(tweet.legacy.entities?.urls) && tweet.legacy.entities.urls.length) {
tweet.legacy.entities.urls.forEach((url: TcoExpansion) => {
let newURL = url.expanded_url;

View file

@ -313,7 +313,9 @@ router.get('/owoembed', async (request: IRequest) => {
provider_name:
searchParams.get('deprecated') === 'true'
? Strings.DEPRECATED_DOMAIN_NOTICE_DISCORD
: (useXbranding ? name : Strings.X_DOMAIN_NOTICE),
: useXbranding
? name
: Strings.X_DOMAIN_NOTICE,
provider_url: url,
title: Strings.DEFAULT_AUTHOR_TEXT,
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',
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_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`,
ERROR_USER_NOT_FOUND: `Sorry, that user doesn't exist :(`,

View file

@ -310,19 +310,19 @@ type GraphQLTweet = {
result: GraphQLTweet;
__typename: 'Tweet';
rest_id: string; // "1674824189176590336",
has_birdwatch_notes: false,
has_birdwatch_notes: false;
core: {
user_results: {
result: GraphQLUser;
}
}
edit_control: unknown,
edit_perspective: unknown,
is_translatable: false,
};
};
edit_control: unknown;
edit_perspective: unknown;
is_translatable: false;
views: {
count: string; // "562"
state: string; // "EnabledWithCount"
}
};
source: string; // "<a href=\"https://mobile.twitter.com\" rel=\"nofollow\">Twitter Web App</a>"
quoted_status_result?: GraphQLTweet;
legacy: {
@ -356,45 +356,54 @@ type GraphQLTweet = {
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[]
}
}
media: TweetMedia[];
};
};
note_tweet: {
is_expandable: boolean;
entity_set: {
hashtags: unknown[];
urls: unknown[];
user_mentions: unknown[];
},
};
note_tweet_results: {
result: {
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'
}
}[]
}
}
}
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: {
@ -403,82 +412,91 @@ type TweetTombstone = {
rtl: boolean; // false;
text: string; // "Youre 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',
entryType: 'TimelineTimelineItem';
__typename: 'TimelineTimelineItem';
itemContent: {
item: 'TimelineTweet',
__typename: 'TimelineTweet',
item: 'TimelineTweet';
__typename: 'TimelineTweet';
tweet_results: {
result: GraphQLTweet|TweetTombstone;
}
}
}
}
result: GraphQLTweet | TweetTombstone;
};
};
};
};
type GraphQLConversationThread = {
entryId: `conversationthread-${number}`; // "conversationthread-1674824189176590336"
sortIndex: string;
}
};
type GraphQLTimelineEntry = GraphQLTimelineTweetEntry|GraphQLConversationThread|unknown;
type GraphQLTimelineEntry =
| GraphQLTimelineTweetEntry
| GraphQLConversationThread
| unknown;
type V2ThreadInstruction = TimeLineAddEntriesInstruction | TimeLineTerminateTimelineInstruction;
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"
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"
}
};
}
code: number; // 144
kind: string; // "NonFatal"
name: string; // "GenericError"
source: string; // "Server"
tracing: {
trace_id: string; // "2e39ff747de237db"
}
}]
];
data: Record<string, never>;
}
};
type GraphQLTweetFoundResponse = {
data: {
threaded_conversation_with_injections_v2: {
instructions: V2ThreadInstruction[]
}
}
}
instructions: V2ThreadInstruction[];
};
};
};
type TweetResultsByRestIdResult = {
errors?: unknown[];
data?: {
tweetResult?: {
result?: {
__typename: 'TweetUnavailable';
reason: 'NsfwLoggedOut'|'Protected';
}|GraphQLTweet
}
}
}
result?:
| {
__typename: 'TweetUnavailable';
reason: 'NsfwLoggedOut' | 'Protected';
}
| GraphQLTweet;
};
};
};

View file

@ -1,7 +1,22 @@
export const isGraphQLTweetNotFoundResponse = (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 isGraphQLTweetNotFoundResponse = (
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 => {
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.message).toEqual('User not found');
expect(response.user).toBeUndefined();
});
});