mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-14 11:51:47 +00:00
WIP 2
This commit is contained in:
@ -2,7 +2,9 @@ import { DefaultPresetList, Preset } from './types';
|
||||
|
||||
import style from './style.css?inline';
|
||||
|
||||
import { createPluginBuilder } from '../utils/builder';
|
||||
import { createPlugin } from '@/utils';
|
||||
import { onConfigChange, onMainLoad } from '@/plugins/downloader/main';
|
||||
import { onPlayerApiReady, onRendererLoad } from '@/plugins/downloader/renderer';
|
||||
|
||||
export type DownloaderPluginConfig = {
|
||||
enabled: boolean;
|
||||
@ -13,7 +15,7 @@ export type DownloaderPluginConfig = {
|
||||
playlistMaxItems?: number;
|
||||
}
|
||||
|
||||
const builder = createPluginBuilder('downloader', {
|
||||
export default createPlugin({
|
||||
name: 'Downloader',
|
||||
restartNeeded: true,
|
||||
config: {
|
||||
@ -24,13 +26,14 @@ const builder = createPluginBuilder('downloader', {
|
||||
skipExisting: false,
|
||||
playlistMaxItems: undefined,
|
||||
} as DownloaderPluginConfig,
|
||||
styles: [style],
|
||||
stylesheets: [style],
|
||||
backend: {
|
||||
start: onMainLoad,
|
||||
onConfigChange,
|
||||
},
|
||||
renderer: {
|
||||
start: onRendererLoad,
|
||||
onPlayerApiReady,
|
||||
}
|
||||
});
|
||||
|
||||
export default builder;
|
||||
|
||||
declare global {
|
||||
interface PluginBuilderList {
|
||||
[builder.id]: typeof builder;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,15 +28,16 @@ import {
|
||||
setBadge,
|
||||
} from './utils';
|
||||
|
||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
||||
import { isEnabled } from '@/config/plugins';
|
||||
import { cleanupName, getImage, SongInfo } from '@/providers/song-info';
|
||||
import { getNetFetchAsFetch } from '@/plugins/utils/main';
|
||||
import { cache } from '@/providers/decorators';
|
||||
import { BackendContext } from '@/types/contexts';
|
||||
|
||||
import { YoutubeFormatList, type Preset, DefaultPresetList } from '../types';
|
||||
|
||||
import builder, { DownloaderPluginConfig } from '../index';
|
||||
|
||||
import { fetchFromGenius } from '../../lyrics-genius/main';
|
||||
import { isEnabled } from '../../../config/plugins';
|
||||
import { cleanupName, getImage, SongInfo } from '../../../providers/song-info';
|
||||
import { getNetFetchAsFetch } from '../../utils/main';
|
||||
import { cache } from '../../../providers/decorators';
|
||||
import { DownloaderPluginConfig } from '../index';
|
||||
|
||||
import type { FormatOptions } from 'youtubei.js/dist/src/types/FormatUtils';
|
||||
import type PlayerErrorMessage from 'youtubei.js/dist/src/parser/classes/PlayerErrorMessage';
|
||||
@ -44,7 +45,7 @@ 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';
|
||||
import type { GetPlayerResponse } from '@/types/get-player-response';
|
||||
|
||||
type CustomSongInfo = SongInfo & { trackId?: string };
|
||||
|
||||
@ -89,31 +90,28 @@ export const getCookieFromWindow = async (win: BrowserWindow) => {
|
||||
.join(';');
|
||||
};
|
||||
|
||||
let config: DownloaderPluginConfig = builder.config;
|
||||
let config: DownloaderPluginConfig;
|
||||
|
||||
export default builder.createMain(({ handle, getConfig, on }) => {
|
||||
return {
|
||||
async onLoad(_win) {
|
||||
win = _win;
|
||||
config = await getConfig();
|
||||
export const onMainLoad = async ({ window: _win, getConfig, ipc }: BackendContext<DownloaderPluginConfig>) => {
|
||||
win = _win;
|
||||
config = await getConfig();
|
||||
|
||||
yt = await Innertube.create({
|
||||
cache: new UniversalCache(false),
|
||||
cookie: await getCookieFromWindow(win),
|
||||
generate_session_locally: true,
|
||||
fetch: getNetFetchAsFetch(),
|
||||
});
|
||||
handle('download-song', (url: string) => downloadSong(url));
|
||||
on('video-src-changed', (data: GetPlayerResponse) => {
|
||||
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
|
||||
});
|
||||
handle('download-playlist-request', async (_event, url: string) => downloadPlaylist(url));
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
config = newConfig;
|
||||
}
|
||||
};
|
||||
});
|
||||
yt = await Innertube.create({
|
||||
cache: new UniversalCache(false),
|
||||
cookie: await getCookieFromWindow(win),
|
||||
generate_session_locally: true,
|
||||
fetch: getNetFetchAsFetch(),
|
||||
});
|
||||
ipc.handle('download-song', (url: string) => downloadSong(url));
|
||||
ipc.on('video-src-changed', (data: GetPlayerResponse) => {
|
||||
playingUrl = data.microformat.microformatDataRenderer.urlCanonical;
|
||||
});
|
||||
ipc.handle('download-playlist-request', async (url: string) => downloadPlaylist(url));
|
||||
};
|
||||
|
||||
export const onConfigChange = (newConfig: DownloaderPluginConfig) => {
|
||||
config = newConfig;
|
||||
};
|
||||
|
||||
export async function downloadSong(
|
||||
url: string,
|
||||
@ -319,7 +317,7 @@ async function iterableStreamToTargetFile(
|
||||
contentLength: number,
|
||||
sendFeedback: (str: string, value?: number) => void,
|
||||
increasePlaylistProgress: (value: number) => void = () => {},
|
||||
) {
|
||||
): Promise<Uint8Array | null> {
|
||||
const chunks = [];
|
||||
let downloaded = 0;
|
||||
for await (const chunk of stream) {
|
||||
@ -379,6 +377,7 @@ async function iterableStreamToTargetFile(
|
||||
} finally {
|
||||
releaseFFmpegMutex();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const getCoverBuffer = cache(async (url: string) => {
|
||||
|
||||
@ -4,9 +4,12 @@ import { downloadPlaylist } from './main';
|
||||
import { defaultMenuDownloadLabel, getFolder } from './main/utils';
|
||||
import { DefaultPresetList } from './types';
|
||||
|
||||
import builder from './index';
|
||||
import type { MenuContext } from '@/types/contexts';
|
||||
import type { MenuTemplate } from '@/menu';
|
||||
|
||||
export default builder.createMenu(async ({ getConfig, setConfig }) => {
|
||||
import type { DownloaderPluginConfig } from './index';
|
||||
|
||||
export const onMenu = async ({ getConfig, setConfig }: MenuContext<DownloaderPluginConfig>): Promise<MenuTemplate> => {
|
||||
const config = await getConfig();
|
||||
|
||||
return [
|
||||
@ -46,4 +49,4 @@ export default builder.createMenu(async ({ getConfig, setConfig }) => {
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import downloadHTML from './templates/download.html?raw';
|
||||
|
||||
import builder from './index';
|
||||
import defaultConfig from '@/config/defaults';
|
||||
import { getSongMenu } from '@/providers/dom-elements';
|
||||
import { getSongInfo } from '@/providers/song-info-front';
|
||||
|
||||
import defaultConfig from '../../config/defaults';
|
||||
import { getSongMenu } from '../../providers/dom-elements';
|
||||
import { ElementFromHtml } from '../utils/renderer';
|
||||
import { getSongInfo } from '../../providers/song-info-front';
|
||||
|
||||
import type { RendererContext } from '@/types/contexts';
|
||||
|
||||
import type { DownloaderPluginConfig } from './index';
|
||||
|
||||
let menu: Element | null = null;
|
||||
let progress: Element | null = null;
|
||||
@ -13,70 +16,67 @@ const downloadButton = ElementFromHtml(downloadHTML);
|
||||
|
||||
let doneFirstLoad = false;
|
||||
|
||||
export default builder.createRenderer(({ invoke, on }) => {
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
export const onRendererLoad = ({ ipc }: RendererContext<DownloaderPluginConfig>) => {
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
// Selector of first button which is always "Start Radio"
|
||||
?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint')
|
||||
?.getAttribute('href');
|
||||
if (videoUrl) {
|
||||
if (videoUrl.startsWith('watch?')) {
|
||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||
}
|
||||
|
||||
if (videoUrl.includes('?playlist=')) {
|
||||
ipc.invoke('download-playlist-request', videoUrl);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
videoUrl = getSongInfo().url || window.location.href;
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
return {
|
||||
onLoad() {
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
// Selector of first button which is always "Start Radio"
|
||||
?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint')
|
||||
?.getAttribute('href');
|
||||
if (videoUrl) {
|
||||
if (videoUrl.startsWith('watch?')) {
|
||||
videoUrl = defaultConfig.url + '/' + videoUrl;
|
||||
}
|
||||
|
||||
if (videoUrl.includes('?playlist=')) {
|
||||
invoke('download-playlist-request', videoUrl);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
videoUrl = getSongInfo().url || window.location.href;
|
||||
}
|
||||
|
||||
invoke('download-song', videoUrl);
|
||||
};
|
||||
|
||||
on('downloader-feedback', (feedback: string) => {
|
||||
if (progress) {
|
||||
progress.innerHTML = feedback || 'Download';
|
||||
} else {
|
||||
console.warn('Cannot update progress');
|
||||
}
|
||||
});
|
||||
},
|
||||
onPlayerApiReady() {
|
||||
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
ipc.invoke('download-song', videoUrl);
|
||||
};
|
||||
});
|
||||
|
||||
ipc.on('downloader-feedback', (feedback: string) => {
|
||||
if (progress) {
|
||||
progress.innerHTML = feedback || 'Download';
|
||||
} else {
|
||||
console.warn('Cannot update progress');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const onPlayerApiReady = () => {
|
||||
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user