Implement direct media (d.pxtwitter.com)

This commit is contained in:
dangered wolf 2022-07-15 21:16:31 -04:00
parent 4a2c06da53
commit b25b81db3a
No known key found for this signature in database
GPG key ID: 41E4D37680ED8B58
4 changed files with 97 additions and 17 deletions

View file

@ -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',

View file

@ -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');
response = new Response(
await handleStatus(id, parseInt(mediaNumber || 1), userAgent),
{
headers: Constants.RESPONSE_HEADERS,
status: 200
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(
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);

View file

@ -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
View file

@ -0,0 +1,6 @@
/* tweetTypes has all the Twitter API-related types */
export type Flags = {
standard?: boolean;
direct?: boolean;
};