rsshub
Version:
Make RSS Great Again!
161 lines (154 loc) • 5.55 kB
text/typescript
import { google } from 'googleapis';
import { art } from '@/utils/render';
import path from 'node:path';
import { config } from '@/config';
import { youtubeOAuth2Client, exec } from './api/google';
export const getPlaylistItems = (id, part, cache) =>
cache.tryGet(
`youtube:getPlaylistItems:${id}`,
async () => {
const res = await exec((youtube) =>
youtube.playlistItems.list({
part,
playlistId: id,
maxResults: 50, // youtube api param value default is 5
})
);
return res;
},
config.cache.routeExpire,
false
);
export const getPlaylist = (id, part, cache) =>
cache.tryGet(`youtube:getPlaylist:${id}`, async () => {
const res = await exec((youtube) =>
youtube.playlists.list({
part,
id,
})
);
return res;
});
export const getChannelWithId = (id, part, cache) =>
cache.tryGet(`youtube:getChannelWithId:${id}`, async () => {
const res = await exec((youtube) =>
youtube.channels.list({
part,
id,
})
);
return res;
});
export const getChannelWithUsername = (username, part, cache) =>
cache.tryGet(`youtube:getChannelWithUsername:${username}`, async () => {
const res = await exec((youtube) =>
youtube.channels.list({
part,
forUsername: username,
})
);
return res;
});
export const getVideos = (id, part, cache) =>
cache.tryGet(`youtube:getVideos:${id}`, async () => {
const res = await exec((youtube) =>
youtube.videos.list({
part,
id,
})
);
return res;
});
export const getThumbnail = (thumbnails) => thumbnails.maxres || thumbnails.standard || thumbnails.high || thumbnails.medium || thumbnails.default;
export const formatDescription = (description) => description?.replaceAll(/\r\n|\r|\n/g, '<br>');
export const renderDescription = (embed, videoId, img, description) =>
art(path.join(__dirname, 'templates/description.art'), {
embed,
videoId,
img,
description,
});
export const getSubscriptions = async (part, cache) => {
// access tokens expire after one hour
let accessToken = await cache.get('youtube:accessToken', false);
if (!accessToken) {
const data = await youtubeOAuth2Client.getAccessToken();
accessToken = data.token;
await cache.set('youtube:accessToken', accessToken, 3600); // ~3600s
}
youtubeOAuth2Client.setCredentials({ access_token: accessToken, refresh_token: config.youtube.refreshToken });
return cache.tryGet('youtube:getSubscriptions', () => getSubscriptionsRecusive(part), config.cache.routeExpire, false);
};
export async function getSubscriptionsRecusive(part, nextPageToken?) {
const res = await google.youtube('v3').subscriptions.list({
auth: youtubeOAuth2Client,
part,
mine: true,
maxResults: 50,
pageToken: nextPageToken ?? undefined,
});
// recursively get next page
if (res.data.nextPageToken) {
const next = await getSubscriptionsRecusive(part, res.data.nextPageToken);
if (next.data.items) {
res.data.items = [...(res.data.items || []), ...next.data.items];
}
}
return res;
}
// taken from https://webapps.stackexchange.com/a/101153
export const isYouTubeChannelId = (id) => /^UC[\w-]{21}[AQgw]$/.test(id);
export const getLive = (id, cache) =>
cache.tryGet(`youtube:getLive:${id}`, async () => {
const res = await exec((youtube) =>
youtube.search.list({
part: 'snippet',
channelId: id,
eventType: 'live',
type: 'video',
})
);
return res;
});
export const getVideoUrl = (id: string) => `https://www.youtube-nocookie.com/embed/${id}?controls=1&autoplay=1&mute=0`;
// Get the appropriate playlist ID with or without shorts
export const getPlaylistWithShortsFilter = (id: string, filterShorts = true): string => {
// If filtering shorts is enabled
if (filterShorts) {
if (id.startsWith('UC')) {
// For channel IDs (UC...), convert to playlist format without shorts (UULF...)
return 'UULF' + id.slice(2);
} else if (id.startsWith('UU')) {
// For playlist IDs (UU...), convert to playlist format without shorts (UULF...)
return 'UULF' + id.slice(2);
}
}
// If filterShorts is false or the ID format doesn't match known patterns, return original ID
return id;
};
export const callApi = async <T>({ googleApi, youtubeiApi, params }: { googleApi: (params: any) => Promise<T>; youtubeiApi: (params: any) => Promise<T>; params: any }): Promise<T> => {
if (config.youtube?.key) {
try {
return await googleApi(params);
} catch {
return await youtubeiApi(params);
}
}
return await youtubeiApi(params);
};
export default {
getPlaylistItems,
getPlaylist,
getChannelWithId,
getChannelWithUsername,
getVideos,
getThumbnail,
formatDescription,
renderDescription,
getSubscriptions,
getSubscriptionsRecusive,
isYouTubeChannelId,
getLive,
getVideoUrl,
getPlaylistWithShortsFilter,
};