add support for shorts urls, channel handles, channel /c/ and /user/
This commit is contained in:
parent
3d79e7ed93
commit
aff62f48ec
|
@ -128,6 +128,7 @@ app.ws('/savechannel', {
|
||||||
|
|
||||||
const channelId = await validateChannel(ws.data.query.url);
|
const channelId = await validateChannel(ws.data.query.url);
|
||||||
if (!channelId) return sendError(ws, 'Invalid channel URL.');
|
if (!channelId) return sendError(ws, 'Invalid channel URL.');
|
||||||
|
if (typeof channelId !== 'string') return sendError(ws, `Failed to fetch channel ID - ${channelId.error}`)
|
||||||
|
|
||||||
ws.send('DATA - This process is automatic. Your video will start archiving shortly.')
|
ws.send('DATA - This process is automatic. Your video will start archiving shortly.')
|
||||||
ws.send('CAPTCHA - Solving a cryptographic challenge before downloading.')
|
ws.send('CAPTCHA - Solving a cryptographic challenge before downloading.')
|
||||||
|
|
|
@ -1,35 +1,29 @@
|
||||||
function validateVideo(input: string): string | false {
|
function validateVideo(input: string): string | false {
|
||||||
try {
|
try {
|
||||||
const url = new URL(input);
|
const url = new URL(input);
|
||||||
|
let videoId: string = ''
|
||||||
const hostnames = [
|
const hostnames = [
|
||||||
'youtube.com',
|
'youtube.com',
|
||||||
'www.youtube.com',
|
'www.youtube.com',
|
||||||
'm.youtube.com'
|
'm.youtube.com'
|
||||||
];
|
];
|
||||||
|
|
||||||
// basic hostname check
|
|
||||||
if (hostnames.includes(url.hostname)) {
|
if (hostnames.includes(url.hostname)) {
|
||||||
// basic url
|
|
||||||
if (url.pathname === '/watch') {
|
if (url.pathname === '/watch') {
|
||||||
const videoId = url.searchParams.get('v');
|
if (!url.searchParams.get('v')) return false
|
||||||
return videoId || false;
|
videoId = url.searchParams.get('v')!
|
||||||
}
|
} else if (url.pathname.startsWith('/shorts/')) {
|
||||||
|
videoId = url.pathname.replace('/shorts/', '')
|
||||||
// embed url
|
} else return false
|
||||||
const embedMatch = url.pathname.match(/^\/embed\/([a-zA-Z0-9_-]+)/);
|
// removed - embed url
|
||||||
if (embedMatch) {
|
|
||||||
return embedMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// short urls
|
// short urls
|
||||||
if (url.hostname === 'youtu.be') {
|
if (url.hostname === 'youtu.be') {
|
||||||
const videoId = url.pathname.replace(/^\//, '');
|
videoId = url.pathname.replace(/^\//, '');
|
||||||
return videoId || false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (videoId && videoId.match(/[\w\-_]{11}/)) return videoId
|
||||||
return false;
|
return false;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,7 +53,7 @@ function validatePlaylist(input: string): string | false {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateChannel(input: string): Promise<string | false> {
|
async function validateChannel(input: string): Promise<string | { error: string; } | false> {
|
||||||
try {
|
try {
|
||||||
const url = new URL(input);
|
const url = new URL(input);
|
||||||
const hostnames = [
|
const hostnames = [
|
||||||
|
@ -67,19 +61,43 @@ async function validateChannel(input: string): Promise<string | false> {
|
||||||
'www.youtube.com',
|
'www.youtube.com',
|
||||||
'm.youtube.com'
|
'm.youtube.com'
|
||||||
];
|
];
|
||||||
|
const whereIsIt: Record<string, (string|number)[]> = {
|
||||||
|
channel: ['metadata', 'channelMetadataRenderer', 'externalId'],
|
||||||
|
handle: ['responseContext', 'serviceTrackingParams', 0, 'params'],
|
||||||
|
user: ['metadata', 'channelMetadataRenderer', 'externalId']
|
||||||
|
}
|
||||||
|
|
||||||
if (hostnames.includes(url.hostname)) {
|
if (hostnames.includes(url.hostname)) {
|
||||||
// @ urls
|
let whatIsIt = ''
|
||||||
const atMatch = url.pathname.match(/^\/@([a-zA-Z0-9.-]+)/);
|
|
||||||
if (atMatch) {
|
// many thanks to Benjamin Loison (@Benjamin-Loison) for his PHP implementation of this
|
||||||
const channelId = await (await fetch(`https://yt.jaybee.digital/api/channels?part=channels&handle=${atMatch[1]}`)).json()
|
// https://github.com/Benjamin-Loison/YouTube-operational-API/blob/main/channels.php
|
||||||
return channelId['items'][0]['id']
|
// and the stackoverflow answer with all the possible options, thank you.
|
||||||
|
// https://stackoverflow.com/a/75843807
|
||||||
|
|
||||||
|
if (url.pathname.startsWith('/channel/')) { // /channel/[id]
|
||||||
|
return url.pathname.match(/UC[\w\-_]{22}/gm)?.[0] || false
|
||||||
|
} else if (url.pathname.startsWith('/c/')) { // /c/[custom]
|
||||||
|
whatIsIt = 'channel'
|
||||||
|
} else if (url.pathname.startsWith('/user/')) {
|
||||||
|
whatIsIt = 'user'
|
||||||
|
} else if (url.pathname.match(/@[\w\-_.]{3,}/gm)) { // /@[handle]
|
||||||
|
whatIsIt = 'handle'
|
||||||
|
} else return false
|
||||||
|
|
||||||
|
const channelReq = await fetch(`${process.env.METADATA}/getWebpageJson?url=${url}`)
|
||||||
|
if (!channelReq.ok) return {
|
||||||
|
error: `Failed to fetch Youtube with status ${channelReq.status}. Please retry.`
|
||||||
}
|
}
|
||||||
|
|
||||||
// /channel/ and /c/
|
const channelJson = await channelReq.json()
|
||||||
const channelMatch = url.pathname.match(/^\/(channel|c)\/([a-zA-Z0-9_-]+)/);
|
let channelId: string | Record<any, any> = getByPath(channelJson, whereIsIt[whatIsIt]!);
|
||||||
if (channelMatch) {
|
if (whatIsIt == 'handle') {
|
||||||
return channelMatch[2];
|
channelId = channelId.find((c:any) => c.key == 'browse_id').value
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof channelId == 'string' ? channelId : {
|
||||||
|
error: 'Failed to extract channel ID from the Youtube provided JSON.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,4 +107,11 @@ async function validateChannel(input: string): Promise<string | false> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getByPath(obj: Record<any, any>, path: (string|number)[]) {
|
||||||
|
return path.reduce((acc, key) => {
|
||||||
|
if (acc === undefined || acc === null) return undefined
|
||||||
|
return acc[key]
|
||||||
|
}, obj)
|
||||||
|
}
|
||||||
|
|
||||||
export { validateVideo, validatePlaylist, validateChannel }
|
export { validateVideo, validatePlaylist, validateChannel }
|
Loading…
Reference in New Issue