mirror of
https://github.com/CompeyDev/fxtwitter-docker.git
synced 2025-04-06 11:00:54 +01:00
Merge pull request #29 from dangeredwolf/api-multi-video
Multi-Video API
This commit is contained in:
commit
49b854edda
6 changed files with 106 additions and 12 deletions
11
src/api.ts
11
src/api.ts
|
@ -23,6 +23,7 @@ const processMedia = (media: TweetMedia): APIPhoto | APIVideo | null => {
|
||||||
return {
|
return {
|
||||||
url: bestVariant?.url || '',
|
url: bestVariant?.url || '',
|
||||||
thumbnail_url: media.media_url_https,
|
thumbnail_url: media.media_url_https,
|
||||||
|
duration: (media.video_info?.duration_millis || 0) / 1000,
|
||||||
width: media.original_info.width,
|
width: media.original_info.width,
|
||||||
height: media.original_info.height,
|
height: media.original_info.height,
|
||||||
format: bestVariant?.content_type || '',
|
format: bestVariant?.content_type || '',
|
||||||
|
@ -93,6 +94,16 @@ const populateTweetProperties = async (
|
||||||
apiTweet.twitter_card = 'player';
|
apiTweet.twitter_card = 'player';
|
||||||
apiTweet.media = apiTweet.media || {};
|
apiTweet.media = apiTweet.media || {};
|
||||||
apiTweet.media.video = mediaObject as APIVideo;
|
apiTweet.media.video = mediaObject as APIVideo;
|
||||||
|
apiTweet.media.videos = apiTweet.media.videos || [];
|
||||||
|
apiTweet.media.videos.push(mediaObject);
|
||||||
|
|
||||||
|
apiTweet.media.video = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
WARNING:
|
||||||
|
'video is deprecated and will be removed. Please use videos[0] instead.',
|
||||||
|
...mediaObject
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const linkFixer = (tweet: TweetPartial, text: string): string => {
|
||||||
text = text.replace(url.url, url.expanded_url);
|
text = text.replace(url.url, url.expanded_url);
|
||||||
});
|
});
|
||||||
|
|
||||||
text = text.replace(/ ?https:\/\/t\.co\/\w{10}/, '');
|
text = text.replace(/ ?https:\/\/t\.co\/\w{10}/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
@ -49,10 +49,11 @@ export const handleStatus = async (
|
||||||
|
|
||||||
if (flags?.direct && tweet.media) {
|
if (flags?.direct && tweet.media) {
|
||||||
let redirectUrl: string | null = null;
|
let redirectUrl: string | null = null;
|
||||||
if (tweet.media.video) {
|
if (tweet.media.videos) {
|
||||||
redirectUrl = tweet.media.video.url;
|
const { videos } = tweet.media;
|
||||||
|
redirectUrl = (videos[(mediaNumber || 1) - 1] || videos[0]).url;
|
||||||
} else if (tweet.media.photos) {
|
} else if (tweet.media.photos) {
|
||||||
const photos = tweet.media.photos;
|
const { photos } = tweet.media;
|
||||||
redirectUrl = (photos[(mediaNumber || 1) - 1] || photos[0]).url;
|
redirectUrl = (photos[(mediaNumber || 1) - 1] || photos[0]).url;
|
||||||
}
|
}
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
|
@ -68,7 +69,7 @@ export const handleStatus = async (
|
||||||
|
|
||||||
let authorText = getAuthorText(tweet) || Strings.DEFAULT_AUTHOR_TEXT;
|
let authorText = getAuthorText(tweet) || Strings.DEFAULT_AUTHOR_TEXT;
|
||||||
const engagementText = authorText.replace(/ {4}/g, ' ');
|
const engagementText = authorText.replace(/ {4}/g, ' ');
|
||||||
const siteName = Constants.BRANDING_NAME;
|
let siteName = Constants.BRANDING_NAME;
|
||||||
let newText = tweet.text;
|
let newText = tweet.text;
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
|
@ -102,14 +103,15 @@ export const handleStatus = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Video renderer */
|
/* Video renderer */
|
||||||
if (tweet.media?.video) {
|
if (tweet.media?.videos) {
|
||||||
authorText = newText || '';
|
authorText = newText || '';
|
||||||
|
|
||||||
if (tweet?.translation) {
|
if (tweet?.translation) {
|
||||||
authorText = tweet.translation?.text || '';
|
authorText = tweet.translation?.text || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { video } = tweet.media;
|
const { videos } = tweet.media;
|
||||||
|
const video = videos[(mediaNumber || 1) - 1];
|
||||||
|
|
||||||
/* Multiplying by 0.5 is an ugly hack to fix Discord
|
/* Multiplying by 0.5 is an ugly hack to fix Discord
|
||||||
disliking videos that are too large lol */
|
disliking videos that are too large lol */
|
||||||
|
@ -123,6 +125,24 @@ export const handleStatus = async (
|
||||||
sizeMultiplier = 2;
|
sizeMultiplier = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const videoCounter = Strings.VIDEO_COUNT.format({
|
||||||
|
number: String(videos.indexOf(video) + 1),
|
||||||
|
total: String(videos.length)
|
||||||
|
});
|
||||||
|
|
||||||
|
authorText =
|
||||||
|
authorText === Strings.DEFAULT_AUTHOR_TEXT
|
||||||
|
? videoCounter
|
||||||
|
: `${authorText}${authorText ? ' ― ' : ''}${videoCounter}`;
|
||||||
|
|
||||||
|
let siteName = `${Constants.BRANDING_NAME} - ${videoCounter}`;
|
||||||
|
|
||||||
|
if (engagementText) {
|
||||||
|
siteName = `${Constants.BRANDING_NAME} - ${engagementText} - ${videoCounter}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.push(`<meta property="og:site_name" content="${siteName}"/>`);
|
||||||
|
|
||||||
headers.push(
|
headers.push(
|
||||||
`<meta name="twitter:player:stream:content_type" content="${video.format}"/>`,
|
`<meta name="twitter:player:stream:content_type" content="${video.format}"/>`,
|
||||||
`<meta name="twitter:player:height" content="${video.height * sizeMultiplier}"/>`,
|
`<meta name="twitter:player:height" content="${video.height * sizeMultiplier}"/>`,
|
||||||
|
@ -160,15 +180,13 @@ export const handleStatus = async (
|
||||||
authorText =
|
authorText =
|
||||||
authorText === Strings.DEFAULT_AUTHOR_TEXT
|
authorText === Strings.DEFAULT_AUTHOR_TEXT
|
||||||
? photoCounter
|
? photoCounter
|
||||||
: `${authorText} ― ${photoCounter}`;
|
: `${authorText}${authorText ? ' ― ' : ''}${photoCounter}`;
|
||||||
|
|
||||||
let siteName = `${Constants.BRANDING_NAME} - ${photoCounter}`;
|
siteName = `${Constants.BRANDING_NAME} - ${photoCounter}`;
|
||||||
|
|
||||||
if (engagementText) {
|
if (engagementText) {
|
||||||
siteName = `${Constants.BRANDING_NAME} - ${engagementText} - ${photoCounter}`;
|
siteName = `${Constants.BRANDING_NAME} - ${engagementText} - ${photoCounter}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.push(`<meta property="og:site_name" content="${siteName}"/>`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.push(
|
headers.push(
|
||||||
|
|
|
@ -72,6 +72,7 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while
|
||||||
TRANSLATE_TEXT: `═ ↘️ Translated from {language} ═════`,
|
TRANSLATE_TEXT: `═ ↘️ Translated from {language} ═════`,
|
||||||
TRANSLATE_TEXT_INTL: `═ ↘️ {source} ➡️ {destination} ═════`,
|
TRANSLATE_TEXT_INTL: `═ ↘️ {source} ➡️ {destination} ═════`,
|
||||||
PHOTO_COUNT: `Photo {number} of {total}`,
|
PHOTO_COUNT: `Photo {number} of {total}`,
|
||||||
|
VIDEO_COUNT: `Video {number} of {total}`,
|
||||||
|
|
||||||
SINGULAR_DAY_LEFT: 'day left',
|
SINGULAR_DAY_LEFT: 'day left',
|
||||||
PLURAL_DAYS_LEFT: 'days left',
|
PLURAL_DAYS_LEFT: 'days left',
|
||||||
|
|
2
src/types.d.ts
vendored
2
src/types.d.ts
vendored
|
@ -101,6 +101,7 @@ interface APIVideo {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
format: string;
|
format: string;
|
||||||
|
duration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface APITweet {
|
interface APITweet {
|
||||||
|
@ -125,6 +126,7 @@ interface APITweet {
|
||||||
external?: APIExternalMedia;
|
external?: APIExternalMedia;
|
||||||
photos?: APIPhoto[];
|
photos?: APIPhoto[];
|
||||||
video?: APIVideo;
|
video?: APIVideo;
|
||||||
|
videos?: APIVideo[];
|
||||||
mosaic?: APIMosaicPhoto;
|
mosaic?: APIMosaicPhoto;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -114,13 +114,14 @@ test('API fetch video Tweet', async () => {
|
||||||
expect(tweet.lang).toEqual('en');
|
expect(tweet.lang).toEqual('en');
|
||||||
expect(tweet.replying_to).toBeNull();
|
expect(tweet.replying_to).toBeNull();
|
||||||
expect(tweet.media?.video).toBeTruthy();
|
expect(tweet.media?.video).toBeTruthy();
|
||||||
const video = tweet.media?.video as APIVideo;
|
const video = tweet.media?.videos?.[0] as APIVideo;
|
||||||
expect(video.url).toEqual(
|
expect(video.url).toEqual(
|
||||||
'https://video.twimg.com/amplify_video/854415175776059393/vid/720x720/dNEi0crU-jA4mTtr.mp4'
|
'https://video.twimg.com/amplify_video/854415175776059393/vid/720x720/dNEi0crU-jA4mTtr.mp4'
|
||||||
);
|
);
|
||||||
expect(video.thumbnail_url).toEqual('https://pbs.twimg.com/media/C9t-btLVoAEqZI1.jpg');
|
expect(video.thumbnail_url).toEqual('https://pbs.twimg.com/media/C9t-btLVoAEqZI1.jpg');
|
||||||
expect(video.width).toEqual(1596);
|
expect(video.width).toEqual(1596);
|
||||||
expect(video.height).toEqual(1600);
|
expect(video.height).toEqual(1600);
|
||||||
|
expect(video.duration).toEqual(65.667);
|
||||||
expect(video.format).toEqual('video/mp4');
|
expect(video.format).toEqual('video/mp4');
|
||||||
expect(video.type).toEqual('video');
|
expect(video.type).toEqual('video');
|
||||||
});
|
});
|
||||||
|
@ -179,3 +180,64 @@ test('API fetch multi-photo Tweet', async () => {
|
||||||
'https://mosaic.fxtwitter.com/webp/1554870933449482240/FZQCeMmXwAAOJTt/FZQCl-lWIAMtoW9/FZQCsQPX0AIbY6H/FZQCxmLXEAMST4q'
|
'https://mosaic.fxtwitter.com/webp/1554870933449482240/FZQCeMmXwAAOJTt/FZQCl-lWIAMtoW9/FZQCsQPX0AIbY6H/FZQCxmLXEAMST4q'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('API fetch multi-video Tweet', async () => {
|
||||||
|
const result = await cacheWrapper(
|
||||||
|
new Request('https://api.fxtwitter.com/dangeredwolf/status/1557914172763127808', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: botHeaders
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.status).toEqual(200);
|
||||||
|
const response = (await result.json()) as APIResponse;
|
||||||
|
expect(response).toBeTruthy();
|
||||||
|
expect(response.code).toEqual(200);
|
||||||
|
expect(response.message).toEqual('OK');
|
||||||
|
|
||||||
|
const tweet = response.tweet as APITweet;
|
||||||
|
expect(tweet).toBeTruthy();
|
||||||
|
expect(tweet.url).toEqual(
|
||||||
|
'https://twitter.com/dangeredwolf/status/1557914172763127808'
|
||||||
|
);
|
||||||
|
expect(tweet.id).toEqual('1557914172763127808');
|
||||||
|
expect(tweet.text).toEqual('');
|
||||||
|
expect(tweet.author.screen_name?.toLowerCase()).toEqual('dangeredwolf');
|
||||||
|
expect(tweet.author.name).toBeTruthy();
|
||||||
|
expect(tweet.author.avatar_url).toBeTruthy();
|
||||||
|
expect(tweet.author.banner_url).toBeTruthy();
|
||||||
|
expect(tweet.author.avatar_color).toBeTruthy();
|
||||||
|
expect(tweet.twitter_card).toEqual('player');
|
||||||
|
expect(tweet.created_at).toEqual('Fri Aug 12 02:17:38 +0000 2022');
|
||||||
|
expect(tweet.created_timestamp).toEqual(1660270658);
|
||||||
|
expect(tweet.replying_to).toBeNull();
|
||||||
|
expect(tweet.media?.videos).toBeTruthy();
|
||||||
|
const videos = tweet.media?.videos as APIVideo[];
|
||||||
|
expect(videos[0].url).toEqual('https://video.twimg.com/ext_tw_video/1539029945124528130/pu/vid/1662x1080/ZQP4eoQhnGnKcLEb.mp4?tag=14');
|
||||||
|
expect(videos[0].thumbnail_url).toEqual("https://pbs.twimg.com/ext_tw_video_thumb/1539029945124528130/pu/img/6Z1MXMliums60j03.jpg");
|
||||||
|
expect(videos[0].width).toEqual(3548);
|
||||||
|
expect(videos[0].height).toEqual(2304);
|
||||||
|
expect(videos[0].duration).toEqual(37.75);
|
||||||
|
expect(videos[0].format).toEqual('video/mp4');
|
||||||
|
expect(videos[0].type).toEqual('video');
|
||||||
|
expect(videos[1].url).toEqual('https://video.twimg.com/ext_tw_video/1543316856697769984/pu/vid/1920x1080/3fo7b4EnWv2WO8Z1.mp4?tag=14');
|
||||||
|
expect(videos[1].thumbnail_url).toEqual("https://pbs.twimg.com/ext_tw_video_thumb/1543316856697769984/pu/img/eCl67JRWO8r4r8A4.jpg");
|
||||||
|
expect(videos[1].width).toEqual(1920);
|
||||||
|
expect(videos[1].height).toEqual(1080);
|
||||||
|
expect(videos[1].duration).toEqual(71.855);
|
||||||
|
expect(videos[1].format).toEqual('video/mp4');
|
||||||
|
expect(videos[1].type).toEqual('video');
|
||||||
|
expect(videos[2].url).toEqual('https://video.twimg.com/ext_tw_video/1543797953105625088/pu/vid/1920x1080/GHSLxzBrwiDLhLYD.mp4?tag=14');
|
||||||
|
expect(videos[2].thumbnail_url).toEqual("https://pbs.twimg.com/ext_tw_video_thumb/1543797953105625088/pu/img/2eX2QQkd7b2S1YDl.jpg");
|
||||||
|
expect(videos[2].width).toEqual(1920);
|
||||||
|
expect(videos[2].height).toEqual(1080);
|
||||||
|
expect(videos[2].duration).toEqual(22.018);
|
||||||
|
expect(videos[2].format).toEqual('video/mp4');
|
||||||
|
expect(videos[2].type).toEqual('video');
|
||||||
|
expect(videos[3].url).toEqual('https://video.twimg.com/ext_tw_video/1548602342488129536/pu/vid/720x1280/I_D3svYfjBl7_xGS.mp4?tag=14');
|
||||||
|
expect(videos[3].thumbnail_url).toEqual("https://pbs.twimg.com/ext_tw_video_thumb/1548602342488129536/pu/img/V_1u5Nv5BwKBynwv.jpg");
|
||||||
|
expect(videos[3].width).toEqual(720);
|
||||||
|
expect(videos[3].height).toEqual(1280);
|
||||||
|
expect(videos[3].duration).toEqual(25.133);
|
||||||
|
expect(videos[3].format).toEqual('video/mp4');
|
||||||
|
expect(videos[3].type).toEqual('video');
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue