diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 763e854b82..a2aa2f9236 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -892,6 +892,10 @@ "label": "Show time codes", "tooltip": "Show the time codes next to the lyrics" }, + "use-ytm-lyrics-without-proxy": { + "label": "Use YTM lyrics with no proxy", + "tooltip": "Skip the YTM browse proxy and call YouTube Music directly using the app's authenticated session. Useful if you trust your network/region to allow direct browse calls." + }, "convert-chinese-character": { "label": "Convert Chinese character", "submenu": { diff --git a/src/plugins/synced-lyrics/index.ts b/src/plugins/synced-lyrics/index.ts index 533f05bfb1..ce3a953f49 100644 --- a/src/plugins/synced-lyrics/index.ts +++ b/src/plugins/synced-lyrics/index.ts @@ -22,6 +22,7 @@ export default createPlugin({ defaultTextString: '♪', lineEffect: 'fancy', romanization: true, + useYTMLyricsWithoutProxy: false, } satisfies SyncedLyricsPluginConfig as SyncedLyricsPluginConfig, menu, diff --git a/src/plugins/synced-lyrics/menu.ts b/src/plugins/synced-lyrics/menu.ts index 9f4fcc6d93..d0f61b4d31 100644 --- a/src/plugins/synced-lyrics/menu.ts +++ b/src/plugins/synced-lyrics/menu.ts @@ -233,5 +233,20 @@ export const menu = async ( }); }, }, + { + label: t( + 'plugins.synced-lyrics.menu.use-ytm-lyrics-without-proxy.label', + ), + toolTip: t( + 'plugins.synced-lyrics.menu.use-ytm-lyrics-without-proxy.tooltip', + ), + type: 'checkbox', + checked: config.useYTMLyricsWithoutProxy, + click(item) { + ctx.setConfig({ + useYTMLyricsWithoutProxy: item.checked, + }); + }, + }, ]; }; diff --git a/src/plugins/synced-lyrics/providers/YTMusic.ts b/src/plugins/synced-lyrics/providers/YTMusic.ts index 1888724fd8..6ab21652a2 100644 --- a/src/plugins/synced-lyrics/providers/YTMusic.ts +++ b/src/plugins/synced-lyrics/providers/YTMusic.ts @@ -1,3 +1,5 @@ +import { config } from '../renderer/renderer'; + import type { LyricProvider, LyricResult, SearchSongInfo } from '../types'; import type { MusicPlayerAppElement } from '@/types/music-player-app-element'; @@ -39,7 +41,9 @@ export class YTMusic implements LyricProvider { const { browseId } = lyricsTab?.tabRenderer?.endpoint?.browseEndpoint ?? {}; if (!browseId) return null; - const { contents } = await this.fetchBrowse(browseId); + const browseData = await this.fetchBrowse(browseId); + if (!browseData) return null; + const { contents } = browseData; if (!contents) return null; /* @@ -122,7 +126,11 @@ export class YTMusic implements LyricProvider { }); } - private fetchBrowse(browseId: string) { + private fetchBrowse(browseId: string): Promise { + if (config()?.useYTMLyricsWithoutProxy) { + return this.fetchBrowseDirect(browseId); + } + return fetch(this.PROXIED_ENDPOINT + 'browse?prettyPrint=false', { headers, method: 'POST', @@ -132,6 +140,22 @@ export class YTMusic implements LyricProvider { }), }).then((res) => res.json()) as Promise; } + + // Calls YouTube Music's browse endpoint directly via the app's authenticated + // network manager, which injects the visitor/auth tokens that the request + // would otherwise be missing when called from regions where the unauthenticated + // call is geo-restricted. + private async fetchBrowseDirect(browseId: string): Promise { + const app = document.querySelector('ytmusic-app'); + if (!app) { + return null; + } + + return await app.networkManager.fetch( + '/browse?prettyPrint=false', + { browseId }, + ); + } } interface NextData { diff --git a/src/plugins/synced-lyrics/types.ts b/src/plugins/synced-lyrics/types.ts index a429364406..59982cceed 100644 --- a/src/plugins/synced-lyrics/types.ts +++ b/src/plugins/synced-lyrics/types.ts @@ -10,6 +10,7 @@ export type SyncedLyricsPluginConfig = { showLyricsEvenIfInexact: boolean; lineEffect: LineEffect; romanization: boolean; + useYTMLyricsWithoutProxy: boolean; convertChineseCharacter?: | 'simplifiedToTraditional' | 'traditionalToSimplified'