fix(downloader): apply poToken

- fix #2863
- fix #2780
This commit is contained in:
JellyBrick
2025-01-18 14:12:31 +09:00
parent 8c325b17f8
commit 76e8e7aa7a
3 changed files with 490 additions and 20 deletions

View File

@ -3,26 +3,22 @@ import { join } from 'node:path';
import { randomBytes } from 'node:crypto';
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
import {
ClientType,
Innertube,
UniversalCache,
Utils,
YTNodes,
} from 'youtubei.js';
import { Innertube, UniversalCache, Utils, YTNodes } from 'youtubei.js';
import is from 'electron-is';
import filenamify from 'filenamify';
import { Mutex } from 'async-mutex';
import { createFFmpeg } from '@ffmpeg.wasm/main';
import NodeID3, { TagConstants } from 'node-id3';
import { Window } from 'happy-dom';
import { BG, type BgConfig } from 'bgutils-js';
import {
cropMaxWidth,
getFolder,
sendFeedback as sendFeedback_,
setBadge,
} from './utils';
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
import { isEnabled } from '@/config/plugins';
import registerCallback, {
@ -33,21 +29,17 @@ import registerCallback, {
SongInfoEvent,
} from '@/providers/song-info';
import { getNetFetchAsFetch } from '@/plugins/utils/main';
import { t } from '@/i18n';
import { DefaultPresetList, type Preset, YoutubeFormatList } from '../types';
import type { DownloaderPluginConfig } from '../index';
import type { BackendContext } from '@/types/contexts';
import type { FormatOptions } from 'youtubei.js/dist/src/types/FormatUtils';
import type PlayerErrorMessage from 'youtubei.js/dist/src/parser/classes/PlayerErrorMessage';
import type { Playlist } from 'youtubei.js/dist/src/parser/ytmusic';
import type { VideoInfo } from 'youtubei.js/dist/src/parser/youtube';
import type TrackInfo from 'youtubei.js/dist/src/parser/ytmusic/TrackInfo';
import type { GetPlayerResponse } from '@/types/get-player-response';
type CustomSongInfo = SongInfo & { trackId?: string };
@ -119,6 +111,61 @@ export const onMainLoad = async ({
generate_session_locally: true,
fetch: getNetFetchAsFetch(),
});
const requestKey = 'O43z0dpjhgX20SCx4KAo';
const visitorData = yt.session.context.client.visitorData;
if (visitorData) {
try {
const [width, height] = win.getSize();
// emulate jsdom using linkedom
const window = new Window({
width,
height,
console,
});
const document = window.document;
Object.assign(globalThis, {
window,
document,
});
const bgConfig: BgConfig = {
fetch: getNetFetchAsFetch(),
globalObj: globalThis,
identifier: visitorData,
requestKey,
};
const bgChallenge = await BG.Challenge.create(bgConfig);
const interpreterJavascript =
bgChallenge?.interpreterJavascript
.privateDoNotAccessOrElseSafeScriptWrappedValue;
if (interpreterJavascript) {
// This is a workaround to run the interpreterJavascript code
// Maybe there is a better way to do this (e.g. https://github.com/Siubaak/sval ?)
// eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-call
new Function(interpreterJavascript)();
const poTokenResult = await BG.PoToken.generate({
program: bgChallenge.program,
globalName: bgChallenge.globalName,
bgConfig,
});
yt.session.po_token = poTokenResult.poToken;
}
} finally {
// Bypass TypeScript checks
((x: Partial<typeof globalThis>) => {
delete x.window;
delete x.document;
})(globalThis);
}
}
ipc.handle('download-song', (url: string) => downloadSong(url));
ipc.on('ytmd:video-src-changed', (data: GetPlayerResponse) => {
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
@ -776,13 +823,7 @@ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
// This is used to bypass age restrictions
const getAndroidTvInfo = async (id: string): Promise<VideoInfo> => {
const innertube = await Innertube.create({
client_type: ClientType.TV_EMBEDDED,
generate_session_locally: true,
retrieve_player: true,
fetch: getNetFetchAsFetch(),
});
// GetInfo 404s with the bypass, so we use getBasicInfo instead
// that's fine as we only need the streaming data
return await innertube.getBasicInfo(id, 'TV_EMBEDDED');
return await yt.getBasicInfo(id, 'TV_EMBEDDED');
};