mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-05 18:40:56 +01:00
Implement direct media (d.pxtwitter.com)
This commit is contained in:
parent
4a2c06da53
commit
b25b81db3a
4 changed files with 97 additions and 17 deletions
|
@ -2,6 +2,12 @@ const fakeChromeVersion = '103';
|
|||
|
||||
export const Constants = {
|
||||
BRANDING_NAME: `pxTwitter`,
|
||||
DIRECT_MEDIA_DOMAINS: [
|
||||
'd.pxtwitter.com',
|
||||
'd.twittpr.com',
|
||||
'dl.pxtwitter.com',
|
||||
'dl.twittpr.com'
|
||||
],
|
||||
HOST_URL: `https://pxtwitter.com`,
|
||||
REDIRECT_URL: 'https://github.com/dangeredwolf/pxTwitter',
|
||||
TWITTER_ROOT: 'https://twitter.com',
|
||||
|
|
|
@ -2,15 +2,27 @@ import { Router } from 'itty-router';
|
|||
import { Constants } from './constants';
|
||||
import { handleStatus } from './status';
|
||||
import { Strings } from './strings';
|
||||
import { Flags } from './types';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const statusRequest = async (request: any, event: FetchEvent) => {
|
||||
const { id, mediaNumber } = request.params;
|
||||
const statusRequest = async (request: any, event: FetchEvent, flags: Flags = {}) => {
|
||||
const { handle, id, mediaNumber } = request.params;
|
||||
const url = new URL(request.url);
|
||||
const userAgent = request.headers.get('User-Agent');
|
||||
|
||||
if (userAgent.match(/bot|facebook/gi) !== null) {
|
||||
let isBotUA = userAgent.match(/bot|facebook/gi) !== null;
|
||||
|
||||
if (
|
||||
url.pathname.match(/\/status(es)?\/\d+\.(mp4|png|jpg)/g) !== null ||
|
||||
Constants.DIRECT_MEDIA_DOMAINS.includes(url.hostname)
|
||||
) {
|
||||
console.log('Direct media request by extension');
|
||||
flags.direct = true;
|
||||
}
|
||||
|
||||
if (isBotUA || flags.direct) {
|
||||
console.log('Matched bot UA');
|
||||
// https://developers.cloudflare.com/workers/examples/cache-api/
|
||||
const cacheUrl = new URL(request.url);
|
||||
const cacheKey = new Request(cacheUrl.toString(), request);
|
||||
|
@ -25,13 +37,26 @@ const statusRequest = async (request: any, event: FetchEvent) => {
|
|||
|
||||
console.log('Cache miss');
|
||||
|
||||
let status = await handleStatus(id.match(/\d{2,20}/)?.[0], parseInt(mediaNumber || 1), userAgent, flags);
|
||||
|
||||
if (status instanceof Response) {
|
||||
console.log('handleStatus sent response');
|
||||
response = status;
|
||||
} else {
|
||||
/* Fallback if a person browses to a direct media link with a Tweet without media */
|
||||
if (!isBotUA) {
|
||||
return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}/status/${id}`, 302);
|
||||
}
|
||||
console.log('handleStatus sent embed');
|
||||
|
||||
response = new Response(
|
||||
await handleStatus(id, parseInt(mediaNumber || 1), userAgent),
|
||||
status,
|
||||
{
|
||||
headers: Constants.RESPONSE_HEADERS,
|
||||
status: 200
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Store the fetched response as cacheKey
|
||||
// Use waitUntil so you can return the response without blocking on
|
||||
|
@ -40,10 +65,15 @@ const statusRequest = async (request: any, event: FetchEvent) => {
|
|||
|
||||
return response;
|
||||
} else {
|
||||
return Response.redirect(`${Constants.TWITTER_ROOT}${url.pathname}`, 302);
|
||||
console.log('Matched human UA');
|
||||
return Response.redirect(`${Constants.TWITTER_ROOT}/${handle}/status/${id}`, 302);
|
||||
}
|
||||
};
|
||||
|
||||
const statusDirectMediaRequest = async (request: any, event: FetchEvent) => {
|
||||
return await statusRequest(request, event, { direct: true });
|
||||
};
|
||||
|
||||
const profileRequest = async (request: any, _event: FetchEvent) => {
|
||||
const { handle } = request.params;
|
||||
const url = new URL(request.url);
|
||||
|
@ -55,6 +85,21 @@ const profileRequest = async (request: any, _event: FetchEvent) => {
|
|||
}
|
||||
};
|
||||
|
||||
/* Direct media handlers */
|
||||
router.get('/dl/:handle/status/:id', statusDirectMediaRequest);
|
||||
router.get('/dl/:handle/status/:id/photo/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dl/:handle/status/:id/video/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dl/:handle/statuses/:id', statusDirectMediaRequest);
|
||||
router.get('/dl/:handle/statuses/:id/photo/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dl/:handle/statuses/:id/video/:mediaNumber', statusDirectMediaRequest);
|
||||
|
||||
router.get('/dir/:handle/status/:id', statusDirectMediaRequest);
|
||||
router.get('/dir/:handle/status/:id/photo/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dir/:handle/status/:id/video/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dir/:handle/statuses/:id', statusDirectMediaRequest);
|
||||
router.get('/dir/:handle/statuses/:id/photo/:mediaNumber', statusDirectMediaRequest);
|
||||
router.get('/dir/:handle/statuses/:id/video/:mediaNumber', statusDirectMediaRequest);
|
||||
|
||||
/* Handlers for Twitter statuses */
|
||||
router.get('/:handle/status/:id', statusRequest);
|
||||
router.get('/:handle/status/:id/photo/:mediaNumber', statusRequest);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { renderCard } from './card';
|
|||
import { handleQuote } from './quote';
|
||||
import { sanitizeText } from './utils';
|
||||
import { Strings } from './strings';
|
||||
import { Flags } from './types';
|
||||
|
||||
export const returnError = (error: string) => {
|
||||
return Strings.BASE_HTML.format({
|
||||
|
@ -20,8 +21,10 @@ export const returnError = (error: string) => {
|
|||
export const handleStatus = async (
|
||||
status: string,
|
||||
mediaNumber?: number,
|
||||
userAgent?: string
|
||||
): Promise<string> => {
|
||||
userAgent?: string,
|
||||
flags?: Flags
|
||||
): Promise<string | Response> => {
|
||||
console.log('Direct?', flags?.direct);
|
||||
const conversation = await fetchUsingGuest(status);
|
||||
|
||||
const tweet = conversation?.globalObjects?.tweets?.[status] || {};
|
||||
|
@ -42,6 +45,8 @@ export const handleStatus = async (
|
|||
`<meta content="Twitter" property="al:android:app_name"/>`
|
||||
];
|
||||
|
||||
let redirectMedia = '';
|
||||
|
||||
/* Fallback for if Tweet did not load */
|
||||
if (typeof tweet.full_text === 'undefined') {
|
||||
console.log('Invalid status, got tweet ', tweet, ' conversation ', conversation);
|
||||
|
@ -166,6 +171,11 @@ export const handleStatus = async (
|
|||
/* Inline helper function for handling media */
|
||||
const processMedia = (media: TweetMedia) => {
|
||||
if (media.type === 'photo') {
|
||||
if (flags?.direct && typeof media.media_url_https === 'string') {
|
||||
redirectMedia = media.media_url_https;
|
||||
return;
|
||||
}
|
||||
|
||||
headers.push(
|
||||
`<meta name="twitter:image" content="${media.media_url_https}"/>`,
|
||||
`<meta property="og:image" content="${media.media_url_https}"/>`
|
||||
|
@ -185,6 +195,18 @@ export const handleStatus = async (
|
|||
pushedCardType = true;
|
||||
}
|
||||
} else if (media.type === 'video' || media.type === 'animated_gif') {
|
||||
|
||||
// Find the variant with the highest bitrate
|
||||
let bestVariant = media.video_info?.variants?.reduce?.((a, b) =>
|
||||
(a.bitrate ?? 0) > (b.bitrate ?? 0) ? a : b
|
||||
);
|
||||
|
||||
if (flags?.direct && bestVariant?.url) {
|
||||
console.log(`Redirecting to ${bestVariant.url}`);
|
||||
redirectMedia = bestVariant.url;
|
||||
return;
|
||||
}
|
||||
|
||||
headers.push(`<meta name="twitter:image" content="${media.media_url_https}"/>`);
|
||||
|
||||
if (userAgent && userAgent?.indexOf?.('Discord') > -1) {
|
||||
|
@ -193,11 +215,6 @@ export const handleStatus = async (
|
|||
|
||||
authorText = encodeURIComponent(text);
|
||||
|
||||
// Find the variant with the highest bitrate
|
||||
let bestVariant = media.video_info?.variants?.reduce?.((a, b) =>
|
||||
(a.bitrate ?? 0) > (b.bitrate ?? 0) ? a : b
|
||||
);
|
||||
|
||||
headers.push(
|
||||
`<meta name="twitter:card" content="player"/>`,
|
||||
`<meta name="twitter:player:stream" content="${bestVariant?.url}"/>`,
|
||||
|
@ -234,6 +251,12 @@ export const handleStatus = async (
|
|||
processMedia(firstMedia);
|
||||
}
|
||||
|
||||
if (flags?.direct && redirectMedia) {
|
||||
let response = Response.redirect(redirectMedia, 302)
|
||||
console.log(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (mediaList.length > 1) {
|
||||
authorText = `Photo ${actualMediaNumber + 1} of ${mediaList.length}`;
|
||||
headers.push(
|
||||
|
|
6
src/types.ts
Normal file
6
src/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* tweetTypes has all the Twitter API-related types */
|
||||
|
||||
export type Flags = {
|
||||
standard?: boolean;
|
||||
direct?: boolean;
|
||||
};
|
Loading…
Add table
Reference in a new issue