RSSHub/lib/routes/youtube/utils.ts

169 lines
5.3 KiB
TypeScript

import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);
import { google } from 'googleapis';
const { OAuth2 } = google.auth;
import { art } from '@/utils/render';
import path from 'node:path';
import { config } from '@/config';
let count = 0;
const youtube = {};
if (config.youtube && config.youtube.key) {
const keys = config.youtube.key.split(',');
for (const [index, key] of keys.entries()) {
if (key) {
youtube[index] = google.youtube({
version: 'v3',
auth: key,
});
count = index + 1;
}
}
}
let index = -1;
const exec = async (func) => {
let result;
for (let i = 0; i < count; i++) {
index++;
try {
// eslint-disable-next-line no-await-in-loop
result = await func(youtube[index % count]);
break;
} catch {
// console.error(error);
}
}
return result;
};
let youtubeOAuth2Client;
if (config.youtube && config.youtube.clientId && config.youtube.clientSecret && config.youtube.refreshToken) {
youtubeOAuth2Client = new OAuth2(config.youtube.clientId, config.youtube.clientSecret, 'https://developers.google.com/oauthplayground');
youtubeOAuth2Client.setCredentials({ refresh_token: config.youtube.refreshToken });
}
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;
});
// not in use
// export const getVideoAuthor = async (id, part) => {
// 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);
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;
});
const youtubeUtils = {
getPlaylistItems,
getPlaylist,
getChannelWithId,
getChannelWithUsername,
getThumbnail,
formatDescription,
renderDescription,
getSubscriptions,
getSubscriptionsRecusive,
isYouTubeChannelId,
getLive,
};
export default youtubeUtils;