Ran Prettier again

This commit is contained in:
dangered wolf 2022-07-14 13:51:59 -04:00
parent 071d9bb440
commit 824e44d4ed
No known key found for this signature in database
GPG key ID: 41E4D37680ED8B58
12 changed files with 158 additions and 127 deletions

View file

@ -1,7 +1,7 @@
{ {
"singleQuote": true, "singleQuote": true,
"semi": true, "semi": true,
"trailingComma": "es5", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"printWidth": 90, "printWidth": 90,
"arrowParens": "avoid", "arrowParens": "avoid",

View file

@ -18,7 +18,7 @@ export const Constants = {
'include_ext_media_color=true', 'include_ext_media_color=true',
'include_ext_media_availability=true', 'include_ext_media_availability=true',
'include_ext_sensitive_media_warning=true', 'include_ext_sensitive_media_warning=true',
'simple_quoted_tweet=true', 'simple_quoted_tweet=true'
].join('&'), ].join('&'),
BASE_HEADERS: { BASE_HEADERS: {
'sec-ch-ua': `".Not/A)Brand";v="99", "Google Chrome";v="${fakeChromeVersion}", "Chromium";v="${fakeChromeVersion}"`, 'sec-ch-ua': `".Not/A)Brand";v="99", "Google Chrome";v="${fakeChromeVersion}", "Chromium";v="${fakeChromeVersion}"`,
@ -36,11 +36,11 @@ export const Constants = {
'Sec-Fetch-Dest': `empty`, 'Sec-Fetch-Dest': `empty`,
'Referer': `https://twitter.com/`, 'Referer': `https://twitter.com/`,
'Accept-Encoding': `gzip, deflate, br`, 'Accept-Encoding': `gzip, deflate, br`,
'Accept-Language': `en`, 'Accept-Language': `en`
}, },
RESPONSE_HEADERS: { RESPONSE_HEADERS: {
'content-type': 'text/html;charset=UTF-8', 'content-type': 'text/html;charset=UTF-8',
"x-powered-by": 'Black Magic', 'x-powered-by': 'Black Magic'
// 'cache-control': 'max-age=1' // 'cache-control': 'max-age=1'
}, },
DEFAULT_COLOR: '#10A3FF' DEFAULT_COLOR: '#10A3FF'

View file

@ -5,7 +5,7 @@ export const fetchUsingGuest = async (status: string): Promise<TimelineBlobParti
let headers: { [header: string]: string } = { let headers: { [header: string]: string } = {
Authorization: Constants.GUEST_BEARER_TOKEN, Authorization: Constants.GUEST_BEARER_TOKEN,
...Constants.BASE_HEADERS, ...Constants.BASE_HEADERS
}; };
/* If all goes according to plan, we have a guest token we can use to call API /* If all goes according to plan, we have a guest token we can use to call API
@ -16,7 +16,7 @@ export const fetchUsingGuest = async (status: string): Promise<TimelineBlobParti
const activate = await fetch(`${Constants.TWITTER_API_ROOT}/1.1/guest/activate.json`, { const activate = await fetch(`${Constants.TWITTER_API_ROOT}/1.1/guest/activate.json`, {
method: 'POST', method: 'POST',
headers: headers, headers: headers,
body: '', body: ''
}); });
/* Let's grab that guest_token so we can use it */ /* Let's grab that guest_token so we can use it */
@ -42,7 +42,7 @@ export const fetchUsingGuest = async (status: string): Promise<TimelineBlobParti
`${Constants.TWITTER_ROOT}/i/api/2/timeline/conversation/${status}.json?${Constants.GUEST_FETCH_PARAMETERS}`, `${Constants.TWITTER_ROOT}/i/api/2/timeline/conversation/${status}.json?${Constants.GUEST_FETCH_PARAMETERS}`,
{ {
method: 'GET', method: 'GET',
headers: headers, headers: headers
} }
) )
).json()) as TimelineBlobPartial; ).json()) as TimelineBlobPartial;

View file

@ -15,5 +15,5 @@ export const Html = {
The best way to embed tweets. The best way to embed tweets.
A work in progress by @dangeredwolf A work in progress by @dangeredwolf
--> -->
<head>{headers}</head>`, <head>{headers}</head>`
}; };

View file

@ -1,16 +1,13 @@
import { Constants } from './constants';
import { Constants } from "./constants";
// https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
const componentToHex = (component: number) => { const componentToHex = (component: number) => {
let hex = component.toString(16); let hex = component.toString(16);
return hex.length === 1 ? "0" + hex : hex; return hex.length === 1 ? '0' + hex : hex;
} };
const rgbToHex = (r: number, g: number, b: number) =>
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
const rgbToHex = (r: number, g: number, b: number) =>
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
export const colorFromPalette = (palette: MediaPlaceholderColor[]) => { export const colorFromPalette = (palette: MediaPlaceholderColor[]) => {
for (let i = 0; i < palette.length; i++) { for (let i = 0; i < palette.length; i++) {
@ -25,4 +22,4 @@ export const colorFromPalette = (palette: MediaPlaceholderColor[]) => {
} }
return Constants.DEFAULT_COLOR; return Constants.DEFAULT_COLOR;
} };

View file

@ -8,16 +8,19 @@ export const calculateTimeLeft = (date: Date) => {
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000); const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return { days, hours, minutes, seconds }; return { days, hours, minutes, seconds };
} };
export const calculateTimeLeftString = (date: Date) => { export const calculateTimeLeftString = (date: Date) => {
const { days, hours, minutes, seconds } = calculateTimeLeft(date); const { days, hours, minutes, seconds } = calculateTimeLeft(date);
const daysString = days > 0 ? `${days} ${days === 1 ? 'day left' : 'days left'}` : ''; const daysString = days > 0 ? `${days} ${days === 1 ? 'day left' : 'days left'}` : '';
const hoursString = hours > 0 ? `${hours} ${hours === 1 ? 'hour left' : 'hours left'}` : ''; const hoursString =
const minutesString = minutes > 0 ? `${minutes} ${minutes === 1 ? 'minute left' : 'minutes left'}` : ''; hours > 0 ? `${hours} ${hours === 1 ? 'hour left' : 'hours left'}` : '';
const secondsString = seconds > 0 ? `${seconds} ${seconds === 1 ? 'second left' : 'seconds left'}` : ''; const minutesString =
minutes > 0 ? `${minutes} ${minutes === 1 ? 'minute left' : 'minutes left'}` : '';
const secondsString =
seconds > 0 ? `${seconds} ${seconds === 1 ? 'second left' : 'seconds left'}` : '';
return daysString || hoursString || minutesString || secondsString || 'Final results'; return daysString || hoursString || minutesString || secondsString || 'Final results';
} };
export const renderPoll = async (card: TweetCard): Promise<string> => { export const renderPoll = async (card: TweetCard): Promise<string> => {
let str = '\n\n'; let str = '\n\n';
@ -29,22 +32,34 @@ export const renderPoll = async (card: TweetCard): Promise<string> => {
let totalVotes = 0; let totalVotes = 0;
let timeLeft = ''; let timeLeft = '';
if (typeof values !== "undefined" && typeof values.end_datetime_utc !== "undefined") { if (typeof values !== 'undefined' && typeof values.end_datetime_utc !== 'undefined') {
const date = new Date(values.end_datetime_utc.string_value); const date = new Date(values.end_datetime_utc.string_value);
timeLeft = calculateTimeLeftString(date); timeLeft = calculateTimeLeftString(date);
} }
if (typeof values !== "undefined" && typeof values.choice1_count !== "undefined" && typeof values.choice2_count !== "undefined") { if (
choices[values.choice1_label?.string_value || ''] = parseInt(values.choice1_count.string_value); typeof values !== 'undefined' &&
typeof values.choice1_count !== 'undefined' &&
typeof values.choice2_count !== 'undefined'
) {
choices[values.choice1_label?.string_value || ''] = parseInt(
values.choice1_count.string_value
);
totalVotes += parseInt(values.choice1_count.string_value); totalVotes += parseInt(values.choice1_count.string_value);
choices[values.choice2_label?.string_value || ''] = parseInt(values.choice2_count.string_value); choices[values.choice2_label?.string_value || ''] = parseInt(
values.choice2_count.string_value
);
totalVotes += parseInt(values.choice2_count.string_value); totalVotes += parseInt(values.choice2_count.string_value);
if (typeof values.choice3_count !== "undefined") { if (typeof values.choice3_count !== 'undefined') {
choices[values.choice3_label?.string_value || ''] = parseInt(values.choice3_count.string_value); choices[values.choice3_label?.string_value || ''] = parseInt(
values.choice3_count.string_value
);
totalVotes += parseInt(values.choice3_count.string_value); totalVotes += parseInt(values.choice3_count.string_value);
} }
if (typeof values.choice4_count !== "undefined") { if (typeof values.choice4_count !== 'undefined') {
choices[values.choice4_label?.string_value || ''] = parseInt(values.choice4_count.string_value); choices[values.choice4_label?.string_value || ''] = parseInt(
values.choice4_count.string_value
);
totalVotes += parseInt(values.choice4_count.string_value); totalVotes += parseInt(values.choice4_count.string_value);
} }
} else { } else {
@ -65,4 +80,4 @@ ${label}  (${Math.round((votes / totalVotes || 0) * 100)}%)
console.log(str); console.log(str);
return str; return str;
} };

View file

@ -1,3 +1,3 @@
export const handleQuote = (quote: TweetPartial): string | null => { export const handleQuote = (quote: TweetPartial): string | null => {
return null; return null;
} };

View file

@ -28,17 +28,17 @@ const statusRequest = async (request: any) => {
const url = new URL(request.url); const url = new URL(request.url);
const userAgent = request.headers.get('User-Agent'); const userAgent = request.headers.get('User-Agent');
if (userAgent.match(/bot/ig) !== null) { if (userAgent.match(/bot/gi) !== null) {
return new Response(await handleStatus(handle, id, parseInt(mediaNumber || 1)), { return new Response(await handleStatus(handle, id, parseInt(mediaNumber || 1)), {
headers: { headers: {
'content-type': 'text/html;charset=UTF-8', 'content-type': 'text/html;charset=UTF-8'
}, },
status: 200 status: 200
}); });
} else { } else {
return Response.redirect(`${Constants.TWITTER_ROOT}${url.pathname}`, 302); return Response.redirect(`${Constants.TWITTER_ROOT}${url.pathname}`, 302);
} }
} };
router.get('/:handle/status/:id', statusRequest); router.get('/:handle/status/:id', statusRequest);
router.get('/:handle/status/:id/photo/:mediaNumber', statusRequest); router.get('/:handle/status/:id/photo/:mediaNumber', statusRequest);
@ -48,29 +48,31 @@ router.get('/:handle/statuses/:id/photo/:mediaNumber', statusRequest);
router.get('/:handle/statuses/:id/video/:mediaNumber', statusRequest); router.get('/:handle/statuses/:id/video/:mediaNumber', statusRequest);
router.get('/owoembed', async (request: any) => { router.get('/owoembed', async (request: any) => {
console.log("THE OWOEMBED HAS BEEN ACCESSED!!!!!!!!!"); console.log('THE OWOEMBED HAS BEEN ACCESSED!!!!!!!!!');
const { searchParams } = new URL(request.url) const { searchParams } = new URL(request.url);
let text = searchParams.get('text') || 'Twitter'; let text = searchParams.get('text') || 'Twitter';
let author = searchParams.get('author') || 'dangeredwolf'; let author = searchParams.get('author') || 'dangeredwolf';
let status = searchParams.get('status') || '1547514042146865153'; let status = searchParams.get('status') || '1547514042146865153';
const test = { const test = {
"author_name": decodeURIComponent(text), author_name: decodeURIComponent(text),
"author_url": `https://twitter.com/${encodeURIComponent(author)}/status/${encodeURIComponent(status)}`, author_url: `https://twitter.com/${encodeURIComponent(
"provider_name": Constants.BRANDING_NAME, author
"provider_url": Constants.REDIRECT_URL, )}/status/${encodeURIComponent(status)}`,
"title": "Twitter", provider_name: Constants.BRANDING_NAME,
"type": "link", provider_url: Constants.REDIRECT_URL,
"version": "1.0" title: 'Twitter',
} type: 'link',
version: '1.0'
};
return new Response(JSON.stringify(test), { return new Response(JSON.stringify(test), {
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json'
}, },
status: 200 status: 200
}); });
}) });
router.all('*', async request => { router.all('*', async request => {
return Response.redirect(Constants.REDIRECT_URL, 307); return Response.redirect(Constants.REDIRECT_URL, 307);

View file

@ -1,13 +1,16 @@
import { Constants } from "./constants"; import { Constants } from './constants';
import { fetchUsingGuest } from "./fetch"; import { fetchUsingGuest } from './fetch';
import { Html } from "./html"; import { Html } from './html';
import { colorFromPalette } from "./palette"; import { colorFromPalette } from './palette';
import { renderPoll } from "./poll"; import { renderPoll } from './poll';
import { handleQuote } from "./quote"; import { handleQuote } from './quote';
export const handleStatus = async (handle: string, status: string, mediaNumber?: number): Promise<string> => { export const handleStatus = async (
handle: string,
status: string,
mediaNumber?: number
): Promise<string> => {
const conversation = await fetchUsingGuest(status); const conversation = await fetchUsingGuest(status);
const tweet = conversation?.globalObjects?.tweets?.[status] || {}; const tweet = conversation?.globalObjects?.tweets?.[status] || {};
/* With v2 conversation API we re-add the user object ot the tweet because /* With v2 conversation API we re-add the user object ot the tweet because
@ -26,11 +29,11 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
`<meta content="Twitter" property="al:ios:app_name"/>`, `<meta content="Twitter" property="al:ios:app_name"/>`,
`<meta content="twitter://status?id=${status}" property="al:android:url"/>`, `<meta content="twitter://status?id=${status}" property="al:android:url"/>`,
`<meta content="com.twitter.android" property="al:android:package"/>`, `<meta content="com.twitter.android" property="al:android:package"/>`,
`<meta content="Twitter" property="al:android:app_name"/>`, `<meta content="Twitter" property="al:android:app_name"/>`
]; ];
// Fallback for if Tweet did not load // Fallback for if Tweet did not load
if (typeof tweet.full_text === "undefined") { if (typeof tweet.full_text === 'undefined') {
headers.push( headers.push(
`<meta content="Twitter" property="og:title"/>`, `<meta content="Twitter" property="og:title"/>`,
`<meta content="Tweet failed to load :(" property="og:description"/>` `<meta content="Tweet failed to load :(" property="og:description"/>`
@ -39,7 +42,7 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
return Html.BASE_HTML.format({ return Html.BASE_HTML.format({
lang: '', lang: '',
headers: headers.join(''), headers: headers.join(''),
tweet: JSON.stringify(tweet), tweet: JSON.stringify(tweet)
}); });
} }
@ -48,7 +51,9 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
const screenName = user?.screen_name || ''; const screenName = user?.screen_name || '';
const name = user?.name || ''; const name = user?.name || '';
const mediaList = Array.from(tweet.extended_entities?.media || tweet.entities?.media || []); const mediaList = Array.from(
tweet.extended_entities?.media || tweet.entities?.media || []
);
let authorText = 'Twitter'; let authorText = 'Twitter';
@ -71,7 +76,10 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
text = text.replace(/ ?https\:\/\/t\.co\/\w{10}/, ''); text = text.replace(/ ?https\:\/\/t\.co\/\w{10}/, '');
} }
if (typeof tweet.extended_entities?.media === 'undefined' && typeof tweet.entities?.media === 'undefined') { if (
typeof tweet.extended_entities?.media === 'undefined' &&
typeof tweet.entities?.media === 'undefined'
) {
let palette = user?.profile_image_extensions_media_color?.palette; let palette = user?.profile_image_extensions_media_color?.palette;
let colorOverride: string = Constants.DEFAULT_COLOR; let colorOverride: string = Constants.DEFAULT_COLOR;
@ -82,7 +90,10 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
headers.push( headers.push(
`<meta content="${colorOverride}" property="theme-color"/>`, `<meta content="${colorOverride}" property="theme-color"/>`,
`<meta property="og:image" content="${user?.profile_image_url_https.replace('_normal', '_200x200')}"/>`, `<meta property="og:image" content="${user?.profile_image_url_https.replace(
'_normal',
'_200x200'
)}"/>`,
`<meta name="twitter:card" content="tweet"/>`, `<meta name="twitter:card" content="tweet"/>`,
`<meta name="twitter:title" content="${name} (@${screenName})"/>`, `<meta name="twitter:title" content="${name} (@${screenName})"/>`,
`<meta name="twitter:image" content="0"/>`, `<meta name="twitter:image" content="0"/>`,
@ -100,30 +111,26 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
if (palette) { if (palette) {
colorOverride = colorFromPalette(palette); colorOverride = colorFromPalette(palette);
} }
headers.push( headers.push(`<meta content="${colorOverride}" property="theme-color"/>`);
`<meta content="${colorOverride}" property="theme-color"/>`
)
const processMedia = (media: TweetMedia) => { const processMedia = (media: TweetMedia) => {
if (media.type === 'photo') { if (media.type === 'photo') {
headers.push( headers.push(`<meta name="twitter:image" content="${media.media_url_https}"/>`);
`<meta name="twitter:image" content="${media.media_url_https}"/>`
);
if (!pushedCardType) { if (!pushedCardType) {
headers.push(`<meta name="twitter:card" content="summary_large_image"/>`); headers.push(`<meta name="twitter:card" content="summary_large_image"/>`);
pushedCardType = true; pushedCardType = true;
} }
} else if (media.type === 'video') { } else if (media.type === 'video') {
headers.push( headers.push(`<meta name="twitter:image" content="${media.media_url_https}"/>`);
`<meta name="twitter:image" content="${media.media_url_https}"/>`
);
authorText = encodeURIComponent(text); authorText = encodeURIComponent(text);
// Find the variant with the highest bitrate // Find the variant with the highest bitrate
let bestVariant = media.video_info?.variants?.reduce?.((a, b) => (a.bitrate || 0) > (b.bitrate || 0) ? a : b); let bestVariant = media.video_info?.variants?.reduce?.((a, b) =>
(a.bitrate || 0) > (b.bitrate || 0) ? a : b
);
headers.push( headers.push(
`<meta name="twitter:card" content="player"/>`, `<meta name="twitter:card" content="player"/>`,
`<meta name="twitter:player:stream" content="${bestVariant?.url}"/>`, `<meta name="twitter:player:stream" content="${bestVariant?.url}"/>`,
@ -137,21 +144,23 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
`<meta name="og:video:type" content="${bestVariant?.content_type}"/>` `<meta name="og:video:type" content="${bestVariant?.content_type}"/>`
); );
} }
} };
let actualMediaNumber = 1; let actualMediaNumber = 1;
console.log('mediaNumber', mediaNumber) console.log('mediaNumber', mediaNumber);
/* You can specify a specific photo in the URL and we'll pull the correct one, /* You can specify a specific photo in the URL and we'll pull the correct one,
otherwise it falls back to first */ otherwise it falls back to first */
if (typeof mediaNumber !== "undefined" && typeof mediaList[mediaNumber - 1] !== "undefined") { if (
console.log(`Media ${mediaNumber} found`) typeof mediaNumber !== 'undefined' &&
typeof mediaList[mediaNumber - 1] !== 'undefined'
) {
console.log(`Media ${mediaNumber} found`);
actualMediaNumber = mediaNumber - 1; actualMediaNumber = mediaNumber - 1;
processMedia(mediaList[actualMediaNumber]); processMedia(mediaList[actualMediaNumber]);
} else { } else {
console.log(`Media ${mediaNumber} not found, ${mediaList.length} total`) console.log(`Media ${mediaNumber} not found, ${mediaList.length} total`);
/* I wish Telegram respected multiple photos in a tweet, /* I wish Telegram respected multiple photos in a tweet,
and that Discord could do the same for 3rd party providers like us */ and that Discord could do the same for 3rd party providers like us */
// media.forEach(media => processMedia(media)); // media.forEach(media => processMedia(media));
@ -159,14 +168,16 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
} }
if (mediaList.length > 1) { if (mediaList.length > 1) {
authorText = `Photo ${(actualMediaNumber + 1)} of ${mediaList.length}`; authorText = `Photo ${actualMediaNumber + 1} of ${mediaList.length}`;
headers.push( headers.push(
`<meta property="og:site_name" content="${Constants.BRANDING_NAME} - Photo ${actualMediaNumber + 1} of ${mediaList.length}"/>` `<meta property="og:site_name" content="${Constants.BRANDING_NAME} - Photo ${
) actualMediaNumber + 1
} of ${mediaList.length}"/>`
);
} else { } else {
headers.push( headers.push(
`<meta property="og:site_name" content="${Constants.BRANDING_NAME}"/>` `<meta property="og:site_name" content="${Constants.BRANDING_NAME}"/>`
) );
} }
headers.push( headers.push(
@ -175,13 +186,13 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
); );
} }
let quoteTweetMaybe = conversation.globalObjects?.tweets?.[tweet.quoted_status_id_str || '0'] || null; let quoteTweetMaybe =
conversation.globalObjects?.tweets?.[tweet.quoted_status_id_str || '0'] || null;
if (quoteTweetMaybe) { if (quoteTweetMaybe) {
const quoteText = handleQuote(quoteTweetMaybe); const quoteText = handleQuote(quoteTweetMaybe);
if (quoteText) { if (quoteText) {
} }
} }
@ -192,9 +203,15 @@ export const handleStatus = async (handle: string, status: string, mediaNumber?:
/* The additional oembed is pulled by Discord to enable improved embeds. /* The additional oembed is pulled by Discord to enable improved embeds.
Telegram does not use this. */ Telegram does not use this. */
headers.push(`<link rel="alternate" href="/owoembed?text=${encodeURIComponent(authorText)}&status=${encodeURIComponent(status)}&author=${encodeURIComponent(user?.screen_name || '')}" type="application/json+oembed" title="${name}">`) headers.push(
`<link rel="alternate" href="/owoembed?text=${encodeURIComponent(
authorText
)}&status=${encodeURIComponent(status)}&author=${encodeURIComponent(
user?.screen_name || ''
)}" type="application/json+oembed" title="${name}">`
);
console.log(JSON.stringify(tweet)) console.log(JSON.stringify(tweet));
/* When dealing with a Tweet of unknown lang, fall back to en */ /* When dealing with a Tweet of unknown lang, fall back to en */
let lang = tweet.lang === 'unk' ? 'en' : tweet.lang || 'en'; let lang = tweet.lang === 'unk' ? 'en' : tweet.lang || 'en';

View file

@ -5,7 +5,7 @@ type TimelineBlobPartial = {
}; };
users: { users: {
[userId: string]: UserPartial; [userId: string]: UserPartial;
} };
}; };
}; };
@ -33,7 +33,7 @@ type TweetMedia = {
display_url: string; display_url: string;
expanded_url: string; expanded_url: string;
ext_media_color?: { ext_media_color?: {
palette?: MediaPlaceholderColor[] palette?: MediaPlaceholderColor[];
}; };
id_str: string; id_str: string;
indices: [number, number]; indices: [number, number];
@ -57,35 +57,35 @@ type TweetMedia = {
}; };
type CardValue = { type CardValue = {
type: 'BOOLEAN' | 'STRING', type: 'BOOLEAN' | 'STRING';
boolean_value: boolean, boolean_value: boolean;
string_value: string, string_value: string;
} };
type TweetCard = { type TweetCard = {
binding_values: { binding_values: {
card_url: CardValue; card_url: CardValue;
choice1_count?: CardValue, choice1_count?: CardValue;
choice2_count?: CardValue, choice2_count?: CardValue;
choice3_count?: CardValue, choice3_count?: CardValue;
choice4_count?: CardValue, choice4_count?: CardValue;
choice1_label?: CardValue, choice1_label?: CardValue;
choice2_label?: CardValue, choice2_label?: CardValue;
choice3_label?: CardValue, choice3_label?: CardValue;
choice4_label?: CardValue, choice4_label?: CardValue;
counts_are_final?: CardValue, counts_are_final?: CardValue;
duration_minutes?: CardValue, duration_minutes?: CardValue;
end_datetime_utc?: CardValue, end_datetime_utc?: CardValue;
}, };
name: string name: string;
} };
type TweetPartial = { type TweetPartial = {
card?: TweetCard; card?: TweetCard;
conversation_id_str: string; conversation_id_str: string;
created_at: string; // date string created_at: string; // date string
display_text_range: [number, number]; display_text_range: [number, number];
entities: { urls?: TcoExpansion[], media?: TweetMedia[] }; entities: { urls?: TcoExpansion[]; media?: TweetMedia[] };
extended_entities: { media?: TweetMedia[] }; extended_entities: { media?: TweetMedia[] };
favorite_count: number; favorite_count: number;
in_reply_to_screen_name?: string; in_reply_to_screen_name?: string;
@ -110,14 +110,14 @@ type UserPartial = {
screen_name: string; screen_name: string;
profile_image_url_https: string; profile_image_url_https: string;
profile_image_extensions_media_color?: { profile_image_extensions_media_color?: {
palette?: MediaPlaceholderColor[] palette?: MediaPlaceholderColor[];
}; };
} };
type MediaPlaceholderColor = { type MediaPlaceholderColor = {
rgb: { rgb: {
red: number; red: number;
green: number; green: number;
blue: number; blue: number;
} };
} };

View file

@ -2,8 +2,8 @@
const componentToHex = (component: number) => { const componentToHex = (component: number) => {
let hex = component.toString(16); let hex = component.toString(16);
return hex.length === 1 ? "0" + hex : hex; return hex.length === 1 ? '0' + hex : hex;
} };
export const rgbToHex = (r: number, g: number, b: number) => export const rgbToHex = (r: number, g: number, b: number) =>
`#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`; `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;

View file

@ -2,20 +2,20 @@ const path = require('path');
module.exports = { module.exports = {
entry: { entry: {
worker: './src/server.ts', worker: './src/server.ts'
}, },
output: { output: {
filename: '[name].js', filename: '[name].js',
path: path.join(__dirname, 'dist'), path: path.join(__dirname, 'dist')
}, },
mode: 'production', mode: 'production',
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js'], extensions: ['.ts', '.tsx', '.js'],
fallback: { util: false }, fallback: { util: false }
}, },
plugins: [], plugins: [],
optimization: { optimization: {
mangleExports: 'size', mangleExports: 'size'
}, },
module: { module: {
rules: [ rules: [
@ -23,9 +23,9 @@ module.exports = {
test: /\.tsx?$/, test: /\.tsx?$/,
loader: 'ts-loader', loader: 'ts-loader',
options: { options: {
transpileOnly: true, transpileOnly: true
}, }
}, }
], ]
}, }
}; };