From 36f7eafa0b8a75d156dd30744cc9478284650d2d Mon Sep 17 00:00:00 2001 From: Cameron Radmore Date: Wed, 29 Jul 2026 19:34:51 -0400 Subject: [PATCH 1/7] Add initial support for clip links --- src/renderer/App.vue | 51 ++++++++++++++++++++- src/renderer/components/FtInput/FtInput.vue | 1 + src/renderer/components/TopNav/TopNav.vue | 48 +++++++++++++++++-- src/renderer/helpers/api/invidious.js | 7 +++ src/renderer/helpers/api/local.js | 15 ++++++ src/renderer/store/modules/utils.js | 13 +++++- 6 files changed, 129 insertions(+), 6 deletions(-) diff --git a/src/renderer/App.vue b/src/renderer/App.vue index e02bdffd7a389..6327f5a452120 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -130,11 +130,19 @@ import packageDetails from '../../package.json' import { openExternalLink, openInternalPath, showToast } from './helpers/utils' import { translateWindowTitle } from './helpers/strings' import { loadLocale } from './i18n/index' +import { getLocalClip } from './helpers/api/local.js' +import { getClipInvidious } from './helpers/api/invidious.js' const route = useRoute() const router = useRouter() const { locale, t } = useI18n() +/** @type {import('vue').ComputedRef<'local' | 'invidious'>} */ +const backendPreference = computed(() => store.getters.getBackendPreference) + +/** @type {import('vue').ComputedRef} */ +const backendFallback = computed(() => store.getters.getBackendFallback) + /** @type {import('vue').ComputedRef} */ const isSideNavOpen = computed(() => store.getters.getIsSideNavOpen) @@ -431,8 +439,18 @@ async function handleYoutubeLink(href, { doCreateNewWindow = false } = {}) { const result = await store.dispatch('getYoutubeUrlInfo', href) switch (result.urlType) { + case 'clip': case 'video': { - const { videoId, timestamp, playlistId } = result + let videoId, timestamp, playlistId + + if (result.urlType === 'video') { + videoId = result.videoId + timestamp = result.timestamp + playlistId = result.playlistId + } else if (result.urlType === 'clip') { + const clipResult = await getClip(result.clipId) + videoId = clipResult.videoId + } const query = {} if (timestamp) { @@ -722,6 +740,37 @@ function handleDragStart(event) { event.stopPropagation() } } + +async function getClip(clipId) { + if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious') { + try { + return await getClipInvidious(clipId) + } catch (err) { + console.error(err) + + if (process.env.SUPPORTS_LOCAL_API && backendFallback.value) { + console.error( + 'Error resolving clip URL. Falling back to Local API' + ) + return await getLocalClip(clipId) + } + } + } else { + try { + return await getLocalClip(clipId) + } catch (err) { + console.error(err) + + if (backendFallback.value) { + console.error( + 'Error resolving clip URL. Falling back to Invidious API' + ) + return await getClipInvidious(clipId) + } + } + } +} +