diff --git a/src/embed/status.ts b/src/embed/status.ts index 42ca7a2..9f3b264 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -4,6 +4,7 @@ import { formatNumber, sanitizeText } from '../helpers/utils'; import { Strings } from '../strings'; import { getAuthorText } from '../helpers/author'; import { statusAPI } from '../api/status'; +import { renderPhoto } from '../render/photo'; export const returnError = (error: string): StatusResponse => { return { @@ -43,7 +44,7 @@ export const handleStatus = async ( }; } - let overrideMedia: APIPhoto | APIVideo | undefined; + let overrideMedia: APIMedia | undefined; // Check if mediaNumber exists, and if that media exists in tweet.media.all. If it does, we'll store overrideMedia variable if (mediaNumber && tweet.media && tweet.media.all && tweet.media.all[mediaNumber - 1]) { @@ -140,20 +141,36 @@ export const handleStatus = async ( newText = `${formatText}\n\n` + `${translation.text}\n\n`; } - /* This Tweet has a video to render. + console.log('overrideMedia', JSON.stringify(overrideMedia)); - Twitter supports multiple videos in a Tweet now. But we have no mechanism to embed more than one. - You can still use /video/:number to get a specific video. Otherwise, it'll pick the first. */ - if (tweet.media?.videos || overrideMedia?.type === 'video') { - authorText = newText || ''; + if (overrideMedia) { + let instructions: ResponseInstructions; - if (tweet?.translation) { - authorText = tweet.translation?.text || ''; + switch (overrideMedia.type) { + case 'photo': + /* This Tweet has a photo to render. */ + instructions = renderPhoto( {tweet: tweet, authorText: authorText, engagementText: engagementText, isOverrideMedia: true, userAgent: userAgent }, overrideMedia as APIPhoto ); + headers.push(...instructions.addHeaders); + if (instructions.authorText) { + authorText = instructions.authorText; + } + if (instructions.siteName) { + siteName = instructions.siteName; + } + break; + case 'video': + /* This Tweet has a video to render. */ + break; } + } else if (tweet.media?.mosaic) { + const instructions = renderPhoto( {tweet: tweet, authorText: authorText, engagementText: engagementText, userAgent: userAgent }, tweet.media?.mosaic ); + headers.push(...instructions.addHeaders); + } else if (tweet.media?.videos) { + authorText = tweet.translation?.text || newText || ''; const videos = tweet.media?.videos; const all = tweet.media?.all || []; - const video = overrideMedia as APIVideo || videos?.[(mediaNumber || 1) - 1]; + const video = videos?.[0]; /* This fix is specific to Discord not wanting to render videos that are too large, or rendering low quality videos too small. @@ -206,62 +223,7 @@ export const handleStatus = async ( ``, `` ); - } - - /* This Tweet has one or more photos to render */ - if (tweet.media?.photos || overrideMedia?.type === 'photo') { - let photo: APIPhoto | APIMosaicPhoto = overrideMedia as APIPhoto || tweet.media?.photos?.[0]; - - /* If there isn't a specified media number and we have a - mosaic response, we'll render it using mosaic */ - if (!overrideMedia && tweet.media?.mosaic) { - photo = { - /* Include dummy height/width for TypeScript reasons. We have a check to make sure we don't use these later. */ - height: 0, - width: 0, - url: tweet.media.mosaic.formats.jpeg, - type: 'photo', - altText: '' - }; - /* If mosaic isn't available or the link calls for a specific photo, - we'll indicate which photo it is out of the total */ - } else if (tweet.media?.all && tweet.media.all.length > 1) { - const { all } = tweet.media; - const photoCounter = Strings.PHOTO_COUNT.format({ - number: String(all.indexOf(photo) + 1), - total: String(all.length) - }); - - authorText = - authorText === Strings.DEFAULT_AUTHOR_TEXT - ? photoCounter - : `${authorText}${authorText ? ' ― ' : ''}${photoCounter}`; - - siteName = `${Constants.BRANDING_NAME} - ${photoCounter}`; - - if (engagementText) { - siteName = `${Constants.BRANDING_NAME} - ${engagementText} - ${photoCounter}`; - } - } - - /* Push the raw photo-related headers */ - headers.push( - ``, - `` - ); - - if (!tweet.media?.mosaic) { - headers.push( - ``, - ``, - ``, - `` - ); - } - } - - /* We have external media available to us (i.e. YouTube videos) */ - if (tweet.media?.external) { + } else if (tweet.media?.external) { const { external } = tweet.media; authorText = newText || ''; headers.push( diff --git a/src/helpers/mosaic.ts b/src/helpers/mosaic.ts index 2ff2d4f..44e5c60 100644 --- a/src/helpers/mosaic.ts +++ b/src/helpers/mosaic.ts @@ -29,10 +29,11 @@ export const handleMosaic = async ( } return { + type: 'mosaic_photo', formats: { jpeg: `${baseUrl}jpeg/${id}${path}`, webp: `${baseUrl}webp/${id}${path}` } - } as unknown as APIMosaicPhoto; + } as APIMosaicPhoto; } }; diff --git a/src/render/photo.ts b/src/render/photo.ts new file mode 100644 index 0000000..0ec6b29 --- /dev/null +++ b/src/render/photo.ts @@ -0,0 +1,53 @@ +import { Constants } from "../constants"; +import { Strings } from "../strings"; + +export const renderPhoto = (properties: RenderProperties, photo: APIPhoto | APIMosaicPhoto): ResponseInstructions => { + const { tweet, engagementText, authorText, isOverrideMedia, userAgent } = properties; + const instructions: ResponseInstructions = { addHeaders: [] }; + + if (!tweet.media?.mosaic || isOverrideMedia) { + + photo = photo as APIPhoto; + + const all = tweet.media?.all as APIMedia[]; + const baseString = all.length === tweet.media?.photos?.length ? Strings.PHOTO_COUNT : Strings.MEDIA_COUNT; + const photoCounter = baseString.format({ + number: String(all.indexOf(photo) + 1), + total: String(all.length) + }); + + console.log('Telegram', userAgent?.indexOf('Telegram')) + if (authorText === Strings.DEFAULT_AUTHOR_TEXT || (userAgent?.indexOf('Telegram') ?? 0) > -1) { + instructions.authorText = photoCounter; + } else { + instructions.authorText = `${authorText}${authorText ? ' ― ' : ''}${photoCounter}`; + } + + if (engagementText && (userAgent?.indexOf('Telegram') ?? 0) === -1) { + instructions.siteName = `${Constants.BRANDING_NAME} - ${engagementText} - ${photoCounter}`; + } else { + instructions.siteName = `${Constants.BRANDING_NAME} - ${photoCounter}`; + } + } + + if (photo.type === 'mosaic_photo' && !isOverrideMedia) { + console.log('Mosaic object:', tweet.media?.mosaic); + instructions.addHeaders = [ + ``, + `` + ]; + } else { + instructions.addHeaders = [ + ``, + ``, + ``, + ``, + ``, + `` + ]; + } + + console.log('Photo render instructions', JSON.stringify(instructions)); + + return instructions; +} \ No newline at end of file diff --git a/src/strings.ts b/src/strings.ts index ec613fa..96ffcc0 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -131,8 +131,9 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while QUOTE_TEXT: `↘️ Quoting {name} (@{screen_name})`, TRANSLATE_TEXT: `↘️ Translated from {language}`, TRANSLATE_TEXT_INTL: `↘️ {source} ➡️ {destination}`, - PHOTO_COUNT: `Photo {number} of {total}`, - VIDEO_COUNT: `Video {number} of {total}`, + PHOTO_COUNT: `Photo {number} / {total}`, + VIDEO_COUNT: `Video {number} / {total}`, + MEDIA_COUNT: `Media {number} / {total}`, SINGULAR_DAY_LEFT: 'day left', PLURAL_DAYS_LEFT: 'days left', diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 574c2c6..f7f6799 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -15,6 +15,22 @@ interface StatusResponse { cacheControl?: string | null; } +interface ResponseInstructions { + addHeaders: string[]; + authorText?: string; + siteName?: string; + engagementText?: string; +} + +interface RenderProperties { + tweet: APITweet; + siteText?: string; + authorText?: string; + engagementText?: string; + isOverrideMedia?: boolean; + userAgent?: string; +} + interface Request { params: { [param: string]: string; @@ -91,15 +107,26 @@ interface APIPoll { time_left_en: string; } -interface APIPhoto { - type: 'photo'; +interface APIMedia { + type: string; url: string; width: number; height: number; +} + +interface APIPhoto extends APIMedia { + type: 'photo'; altText: string; } -interface APIMosaicPhoto { +interface APIVideo extends APIMedia { + type: 'video' | 'gif'; + thumbnail_url: string; + format: string; + duration: number; +} + +interface APIMosaicPhoto extends APIMedia { type: 'mosaic_photo'; formats: { webp: string; @@ -107,16 +134,6 @@ interface APIMosaicPhoto { }; } -interface APIVideo { - type: 'video' | 'gif'; - url: string; - thumbnail_url: string; - width: number; - height: number; - format: string; - duration: number; -} - interface APITweet { id: string; url: string; @@ -140,7 +157,7 @@ interface APITweet { external?: APIExternalMedia; photos?: APIPhoto[]; videos?: APIVideo[]; - all?: (APIPhoto | APIVideo)[]; + all?: APIMedia[]; mosaic?: APIMosaicPhoto; };