Add support for converting youtube handle channels urls to channel_id. Closes #353

This commit is contained in:
arabcoders 2025-07-27 16:44:36 +03:00
parent 7b9144b200
commit 781b767879

View file

@ -1,20 +1,10 @@
<template> <template>
<main class="columns mt-2 is-multiline"> <main class="columns mt-2 is-multiline">
<div class="column is-12" v-if="form?.url && is_yt_handle(form.url)"> <div class="column is-12" v-if="form?.url && is_yt_handle(form.url)">
<Message title="Warning" class="is-background-warning-80 has-text-dark" icon="fas fa-info-circle"> <Message title="Information" class="is-info is-background-info-80 has-text-dark" icon="fas fa-info-circle">
<span> <span>You are using a YouTube link with <b>@handle</b> instead of <b>channel_id</b>. To activate RSS feed
<ul> support for URL click on the <NuxtLink @click="async () => form.url = await convert_url(form.url)"><b>Convert
<li>You are using a YouTube link with handle instead of channel_id. To activate RSS feed support for URL</b></NuxtLink> link.</span>
channel, you need to use
the channel ID. For example, <code>https://www.youtube.com/channel/UCUi3_cffYenmMTuWEsLHzqg</code>
</li>
<li>
To get youtube channel_id simply visit the page, click on <b>more about this channel</b>, scroll down to
<b>share
channel</b>, click on <code>Copy channel id</code>.
</li>
</ul>
</span>
</Message> </Message>
</div> </div>
<div class="column is-12"> <div class="column is-12">
@ -79,7 +69,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>The name is used to identify this specific task.</span> <span class="is-bold">The name is used to identify this specific task.</span>
</span> </span>
</div> </div>
</div> </div>
@ -89,17 +79,20 @@
<label class="label is-inline" for="url"> <label class="label is-inline" for="url">
<span class="icon"><i class="fa-solid fa-link" /></span> <span class="icon"><i class="fa-solid fa-link" /></span>
URL URL
<template v-if="is_yt_handle(form.url)">
- <NuxtLink @click="async () => form.url = await convert_url(form.url)">Convert URL</NuxtLink>
</template>
</label> </label>
<div class="control has-icons-left"> <div class="control has-icons-left">
<input type="url" class="input" id="url" v-model="form.url" :disabled="addInProgress"> <input type="url" class="input" id="url" v-model="form.url"
<span class="icon is-small is-left"><i class="fa-solid fa-link" /></span> :disabled="addInProgress || convertInProgress"
placeholder="https://www.youtube.com/channel/UCUi3_cffYenmMTuWEsLHzqg">
<span class="icon is-small is-left"><i class="fa-solid fa-link"
:class="{ 'fa-spin': convertInProgress }" /></span>
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>The channel or playlist URL. For youtube there is rss feed support if you use URL with <span class="is-bold">The channel or playlist URL.</span>
channel_id or playlist_id. For example, https://www.youtube.com/<span
class="has-text-danger">channel/UCUi3_cffYenmMTuWEsLHzqg</span>
</span>
</span> </span>
</div> </div>
</div> </div>
@ -130,7 +123,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Select the preset to use for this URL. <span class="text-has-danger">If the <span class="is-bold">Select the preset to use for this URL. <span class="text-has-danger">If the
<code>-f, --format</code> <span class="has-text-danger"> <code>-f, --format</code> <span class="has-text-danger">
argument is present in the command line options, the preset and all argument is present in the command line options, the preset and all
it's options will be ignored.</span></span> it's options will be ignored.</span></span>
@ -151,7 +144,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span> <span class="is-bold">
The CRON timer expression to use for this task. If not set, the task will be disabled. For more The CRON timer expression to use for this task. If not set, the task will be disabled. For more
information on CRON expressions, see <NuxtLink to="https://crontab.guru/" target="_blank"> information on CRON expressions, see <NuxtLink to="https://crontab.guru/" target="_blank">
crontab.guru</NuxtLink>. crontab.guru</NuxtLink>.
@ -172,8 +165,8 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Current download folder: <code>{{ get_download_folder() }}</code>. All folders are <span class="is-bold">Current download folder: <code>{{ get_download_folder() }}</code>. All folders
sub-folders of <code>{{ config.app.download_path }}</code>.</span> are sub-folders of <code>{{ config.app.download_path }}</code>.</span>
</span> </span>
</div> </div>
</div> </div>
@ -190,7 +183,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Current output format: <code>{{ get_output_template() }}</code>.</span> <span class="is-bold">Current output format: <code>{{ get_output_template() }}</code>.</span>
</span> </span>
</div> </div>
</div> </div>
@ -210,7 +203,8 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Whether to automatically start downloading or just queue them in paused state.</span> <span class="is-bold">Whether to automatically start downloading or just queue them in paused
state.</span>
</span> </span>
</div> </div>
</div> </div>
@ -230,9 +224,8 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Some URLs like YouTube channels and playlists can be monitored for new videos using RSS <span class="is-bold">Some URLs like YouTube channels/playlists can be monitored using RSS feed,
feed independent of the timer. If enabled, the task will monitor the RSS feed for new videos and this option works regardless of the timer being set or not.</span>
automatically queue them for download if they are not already downloaded.</span>
</span> </span>
</div> </div>
</div> </div>
@ -250,7 +243,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>yt-dlp cli arguments. Check <NuxtLink target="_blank" <span class="is-bold">yt-dlp cli arguments. Check <NuxtLink target="_blank"
to="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#general-options">this page</NuxtLink>. to="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#general-options">this page</NuxtLink>.
For more info. <span class="has-text-danger">Not all options are supported some are ignored. Use For more info. <span class="has-text-danger">Not all options are supported some are ignored. Use
with caution those arguments can break yt-dlp or the frontend.</span> with caution those arguments can break yt-dlp or the frontend.</span>
@ -329,9 +322,10 @@ const config = useConfigStore()
const box = useConfirm() const box = useConfirm()
const showImport = useStorage('showImport', false) const showImport = useStorage('showImport', false)
const convertInProgress = ref<boolean>(false)
const import_string = ref<string>('') const import_string = ref<string>('')
const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))\/?$/ const CHANNEL_REGEX = /^https?:\/\/(?:www\.)?youtube\.com\/(?:(?:channel\/(?<channelId>UC[0-9A-Za-z_-]{22}))|(?:c\/(?<customName>[A-Za-z0-9_-]+))|(?:user\/(?<userName>[A-Za-z0-9_-]+))|(?:@(?<handle>[A-Za-z0-9_-]+)))(?<suffix>\/.*)?\/?$/
const form = reactive<task_item>({ ...props.task }) const form = reactive<task_item>({ ...props.task })
@ -477,10 +471,48 @@ const get_output_template = (): string => {
} }
const is_yt_handle = (url: string): boolean => { const is_yt_handle = (url: string): boolean => {
if (!url || '' === url) {
return false
}
const m = url.match(CHANNEL_REGEX) const m = url.match(CHANNEL_REGEX)
if (m?.groups) { if (m?.groups) {
return !m.groups.channelId return !m.groups.channelId
} }
return false return false
} }
const convert_url = async (url: string): Promise<string> => {
if (!url || '' === url) {
return url
}
const m = url.match(CHANNEL_REGEX)
if (!m?.groups || !m.groups.handle) {
return url
}
const params = new URLSearchParams()
params.append('url', url)
params.append('args', '-I0')
try {
convertInProgress.value = true
const resp = await request('/api/yt-dlp/url/info?' + params.toString(), { credentials: 'include' })
const body = await resp.json()
const channel_id = ag(body, 'channel_id', null)
console.log('convert_url', { url, channel_id, body })
if (channel_id) {
return url.replace(`/@${m.groups.handle}`, `/channel/${channel_id}`)
}
} catch (e: any) {
console.error(e)
toast.error(`Error: ${e.message}`)
} finally {
convertInProgress.value = false
}
return url
}
</script> </script>