diff --git a/.env.example b/.env.example index d094725..b4c372f 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,9 @@ BRANDING_NAME = "FixTweet / FixupX" STANDARD_DOMAIN_LIST = "fxtwitter.com,fixupx.com,twittpr.com" DIRECT_MEDIA_DOMAINS = "d.fxtwitter.com,dl.fxtwitter.com,d.pxtwitter.com,d.twittpr.com,dl.pxtwitter.com,dl.twittpr.com,d.fixupx.com,d.xfixup.com,dl.fixupx.com,dl.xfixup.com" -TEXT_ONLY_DOMAINS = "t.fxtwitter.com,t.twittpr.com,t.fixupx.com,t.xfixup.com" -INSTANT_VIEW_DOMAINS = "i.fxtwitter.com,i.twittpr.com,i.fixupx.com,i.xfixup.com" +TEXT_ONLY_DOMAINS = "t.fxtwitter.com,t.twittpr.com,t.fixupx.com" +INSTANT_VIEW_DOMAINS = "i.fxtwitter.com,i.twittpr.com,i.fixupx.com" +GALLERY_DOMAINS = "g.fxtwitter.com,g.twittpr.com,g.fixupx.com" DEPRECATED_DOMAIN_LIST = "pxtwitter.com,www.pxtwitter.com" DEPRECATED_DOMAIN_EPOCH = "1559320000000000000" MOSAIC_DOMAIN_LIST = "mosaic.fxtwitter.com" diff --git a/esbuild.config.mjs b/esbuild.config.mjs index a660407..9562607 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -37,6 +37,7 @@ let envVariables = [ 'DIRECT_MEDIA_DOMAINS', 'TEXT_ONLY_DOMAINS', 'INSTANT_VIEW_DOMAINS', + 'GALLERY_DOMAINS', 'HOST_URL', 'REDIRECT_URL', 'EMBED_URL', diff --git a/jestconfig.json b/jestconfig.json index 7e51963..799557b 100644 --- a/jestconfig.json +++ b/jestconfig.json @@ -5,10 +5,11 @@ }, "globals": { "BRANDING_NAME": "FixTweet", - "TEXT_ONLY_DOMAINS": "t.fxtwitter.com,t.twittpr.com", - "INSTANT_VIEW_DOMAINS": "i.fxtwitter.com,i.twittpr.com", + "TEXT_ONLY_DOMAINS": "t.fxtwitter.com,t.twittpr.com,t.fixupx.com", + "INSTANT_VIEW_DOMAINS": "i.fxtwitter.com,i.twittpr.com,i.fixupx.com", + "GALLERY_DOMAINS": "g.fxtwitter.com,g.twittpr.com,g.fixupx.com", "STANDARD_DOMAIN_LIST": "fxtwitter.com,fixupx.com,twittpr.com", - "DIRECT_MEDIA_DOMAINS": "d.fxtwitter.com,dl.fxtwitter.com", + "DIRECT_MEDIA_DOMAINS": "d.fxtwitter.com,dl.fxtwitter.com,d.fixupx.com,dl.fixupx.com", "MOSAIC_DOMAIN_LIST": "mosaic.fxtwitter.com", "DEPRECATED_DOMAIN_LIST": "pxtwitter.com,www.pxtwitter.com", "DEPRECATED_DOMAIN_EPOCH": "1559320000000000000", diff --git a/src/constants.ts b/src/constants.ts index 0c2c911..cd8686f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,6 +5,7 @@ export const Constants = { DIRECT_MEDIA_DOMAINS: DIRECT_MEDIA_DOMAINS.split(','), TEXT_ONLY_DOMAINS: TEXT_ONLY_DOMAINS.split(','), INSTANT_VIEW_DOMAINS: INSTANT_VIEW_DOMAINS.split(','), + GALLERY_DOMAINS: GALLERY_DOMAINS.split(','), DEPRECATED_DOMAIN_LIST: DEPRECATED_DOMAIN_LIST.split(','), DEPRECATED_DOMAIN_EPOCH: BigInt(DEPRECATED_DOMAIN_EPOCH), MOSAIC_DOMAIN_LIST: MOSAIC_DOMAIN_LIST.split(','), diff --git a/src/embed/status.ts b/src/embed/status.ts index ca50b7d..22dcc08 100644 --- a/src/embed/status.ts +++ b/src/embed/status.ts @@ -1,3 +1,4 @@ +import { Context } from 'hono'; import { Constants } from '../constants'; import { handleQuote } from '../helpers/quote'; import { formatNumber, sanitizeText, truncateWithEllipsis } from '../helpers/utils'; @@ -7,7 +8,6 @@ import { renderPhoto } from '../render/photo'; import { renderVideo } from '../render/video'; import { renderInstantView } from '../render/instantview'; import { constructTwitterThread } from '../providers/twitter/conversation'; -import { Context } from 'hono'; export const returnError = (c: Context, error: string): Response => { return c.text( @@ -148,6 +148,11 @@ export const handleStatus = async ( } } + /* User requested gallery view, but this isn't a post with media */ + if (flags.gallery && (tweet.media?.all?.length ?? 0) < 1) { + flags.gallery = false; + } + /* At this point, we know we're going to have to create a regular embed because it's not an API or direct media request */ @@ -160,12 +165,17 @@ export const handleStatus = async ( const headers = [ ``, ``, - ``, ``, ``, - `` ]; + if (!flags.gallery) { + headers.push( + ``, + `` + ); + } + /* This little thing ensures if by some miracle a FixTweet embed is loaded in a browser, it will gracefully redirect to the destination instead of just seeing a blank screen. @@ -389,14 +399,20 @@ export const handleStatus = async ( const useCard = tweet.embed_card === 'tweet' ? tweet.quote?.embed_card : tweet.embed_card; + /* Push basic headers relating to author, Tweet text, and site name */ headers.push( ``, - ``, - ``, `` ); + if (!flags.gallery) { + headers.push( + ``, + ``, + ); + } + /* Special reply handling if authorText is not overriden */ if (tweet.replying_to && authorText === Strings.DEFAULT_AUTHOR_TEXT) { authorText = `↪ Replying to @${tweet.replying_to.screen_name}`; @@ -408,20 +424,22 @@ export const handleStatus = async ( authorText = `↪ A part of @${tweet.author.screen_name}'s thread`; } - /* The additional oembed is pulled by Discord to enable improved embeds. - Telegram does not use this. */ - headers.push( - ``.format( - { - base: Constants.HOST_URL, - text: encodeURIComponent(truncateWithEllipsis(authorText, 255)), - deprecatedFlag: flags?.deprecated ? '&deprecated=true' : '', - status: encodeURIComponent(status), - author: encodeURIComponent(tweet.author?.screen_name || ''), - name: tweet.author.name || '' - } - ) - ); + if (!flags.gallery) { + /* The additional oembed is pulled by Discord to enable improved embeds. + Telegram does not use this. */ + headers.push( + ``.format( + { + base: Constants.HOST_URL, + text: encodeURIComponent(truncateWithEllipsis(authorText, 255)), + deprecatedFlag: flags?.deprecated ? '&deprecated=true' : '', + status: encodeURIComponent(status), + author: encodeURIComponent(tweet.author?.screen_name || ''), + name: tweet.author.name || '' + } + ) + ); + } /* When dealing with a Tweet of unknown lang, fall back to en */ const lang = tweet.lang === null ? 'en' : tweet.lang || 'en'; diff --git a/src/realms/twitter/routes/status.ts b/src/realms/twitter/routes/status.ts index 783f11a..9cb24bf 100644 --- a/src/realms/twitter/routes/status.ts +++ b/src/realms/twitter/routes/status.ts @@ -55,6 +55,9 @@ export const statusRequest = async (c: Context) => { } else if (Constants.INSTANT_VIEW_DOMAINS.includes(url.hostname)) { console.log('Forced instant view request'); flags.forceInstantView = true; + } else if (Constants.GALLERY_DOMAINS.includes(url.hostname)) { + console.log('Gallery embed request'); + flags.gallery = true; } else if (prefix === 'dl' || prefix === 'dir') { console.log('Direct media request by path prefix'); flags.direct = true; diff --git a/src/types/env.d.ts b/src/types/env.d.ts index d38e132..d7af39a 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -3,6 +3,7 @@ declare const STANDARD_DOMAIN_LIST: string; declare const DIRECT_MEDIA_DOMAINS: string; declare const TEXT_ONLY_DOMAINS: string; declare const INSTANT_VIEW_DOMAINS: string; +declare const GALLERY_DOMAINS: string; declare const DEPRECATED_DOMAIN_LIST: string; declare const DEPRECATED_DOMAIN_EPOCH: string; declare const HOST_URL: string; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index e5eb0d2..17a5d8e 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -10,6 +10,7 @@ type InputFlags = { isXDomain?: boolean; forceInstantView?: boolean; archive?: boolean; + gallery?: boolean; }; interface StatusResponse {