mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-13 03:11:46 +00:00
feat: run prettier
This commit is contained in:
@ -9,7 +9,11 @@ export const restart = () => restartInternal();
|
||||
export const setupAppControls = () => {
|
||||
ipcMain.on('restart', restart);
|
||||
ipcMain.handle('getDownloadsFolder', () => app.getPath('downloads'));
|
||||
ipcMain.on('reload', () => BrowserWindow.getFocusedWindow()?.webContents.loadURL(config.get('url')));
|
||||
ipcMain.on(
|
||||
'reload',
|
||||
() =>
|
||||
BrowserWindow.getFocusedWindow()?.webContents.loadURL(config.get('url')),
|
||||
);
|
||||
ipcMain.handle('getPath', (_, ...args: string[]) => path.join(...args));
|
||||
};
|
||||
|
||||
@ -25,9 +29,9 @@ function sendToFrontInternal(channel: string, ...args: unknown[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export const sendToFront
|
||||
= process.type === 'browser'
|
||||
? sendToFrontInternal
|
||||
: () => {
|
||||
console.error('sendToFront called from renderer');
|
||||
};
|
||||
export const sendToFront =
|
||||
process.type === 'browser'
|
||||
? sendToFrontInternal
|
||||
: () => {
|
||||
console.error('sendToFront called from renderer');
|
||||
};
|
||||
|
||||
@ -11,7 +11,10 @@ export function singleton<T extends (...params: never[]) => unknown>(fn: T): T {
|
||||
}) as T;
|
||||
}
|
||||
|
||||
export function debounce<T extends (...params: never[]) => unknown>(fn: T, delay: number): T {
|
||||
export function debounce<T extends (...params: never[]) => unknown>(
|
||||
fn: T,
|
||||
delay: number,
|
||||
): T {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return ((...args) => {
|
||||
clearTimeout(timeout);
|
||||
@ -19,13 +22,15 @@ export function debounce<T extends (...params: never[]) => unknown>(fn: T, delay
|
||||
}) as T;
|
||||
}
|
||||
|
||||
export function cache<T extends (...params: P) => R, P extends never[], R>(fn: T): T {
|
||||
export function cache<T extends (...params: P) => R, P extends never[], R>(
|
||||
fn: T,
|
||||
): T {
|
||||
let lastArgs: P;
|
||||
let lastResult: R;
|
||||
return ((...args: P) => {
|
||||
if (
|
||||
args.length !== lastArgs?.length
|
||||
|| args.some((arg, i) => arg !== lastArgs[i])
|
||||
args.length !== lastArgs?.length ||
|
||||
args.some((arg, i) => arg !== lastArgs[i])
|
||||
) {
|
||||
lastArgs = args;
|
||||
lastResult = fn(...args);
|
||||
@ -39,7 +44,10 @@ export function cache<T extends (...params: P) => R, P extends never[], R>(fn: T
|
||||
The following are currently unused, but potentially useful in the future
|
||||
*/
|
||||
|
||||
export function throttle<T extends (...params: unknown[]) => unknown>(fn: T, delay: number): T {
|
||||
export function throttle<T extends (...params: unknown[]) => unknown>(
|
||||
fn: T,
|
||||
delay: number,
|
||||
): T {
|
||||
let timeout: NodeJS.Timeout | undefined;
|
||||
return ((...args) => {
|
||||
if (timeout) {
|
||||
@ -66,7 +74,10 @@ function memoize<T extends (...params: unknown[]) => unknown>(fn: T): T {
|
||||
}) as T;
|
||||
}
|
||||
|
||||
function retry<T extends (...params: unknown[]) => unknown>(fn: T, { retries = 3, delay = 1000 } = {}): T {
|
||||
function retry<T extends (...params: unknown[]) => unknown>(
|
||||
fn: T,
|
||||
{ retries = 3, delay = 1000 } = {},
|
||||
): T {
|
||||
return ((...args) => {
|
||||
try {
|
||||
return fn(...args);
|
||||
|
||||
@ -10,11 +10,9 @@ let protocolHandler: ((cmd: string) => void) | undefined;
|
||||
|
||||
export function setupProtocolHandler(win: BrowserWindow) {
|
||||
if (process.defaultApp && process.argv.length >= 2) {
|
||||
app.setAsDefaultProtocolClient(
|
||||
APP_PROTOCOL,
|
||||
process.execPath,
|
||||
[path.resolve(process.argv[1])],
|
||||
);
|
||||
app.setAsDefaultProtocolClient(APP_PROTOCOL, process.execPath, [
|
||||
path.resolve(process.argv[1]),
|
||||
]);
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient(APP_PROTOCOL);
|
||||
}
|
||||
@ -42,4 +40,3 @@ export default {
|
||||
handleProtocol,
|
||||
changeProtocolHandler,
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
// This is used for to control the songs
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
type Modifiers = (Electron.MouseInputEvent | Electron.MouseWheelInputEvent | Electron.KeyboardInputEvent)['modifiers'];
|
||||
export const pressKey = (window: BrowserWindow, key: string, modifiers: Modifiers = []) => {
|
||||
type Modifiers = (
|
||||
| Electron.MouseInputEvent
|
||||
| Electron.MouseWheelInputEvent
|
||||
| Electron.KeyboardInputEvent
|
||||
)['modifiers'];
|
||||
export const pressKey = (
|
||||
window: BrowserWindow,
|
||||
key: string,
|
||||
modifiers: Modifiers = [],
|
||||
) => {
|
||||
window.webContents.sendInputEvent({
|
||||
type: 'keyDown',
|
||||
modifiers,
|
||||
|
||||
@ -10,7 +10,8 @@ import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||
let songInfo: SongInfo = {} as SongInfo;
|
||||
export const getSongInfo = () => songInfo;
|
||||
|
||||
const $ = <E extends Element = Element>(s: string): E | null => document.querySelector<E>(s);
|
||||
const $ = <E extends Element = Element>(s: string): E | null =>
|
||||
document.querySelector<E>(s);
|
||||
|
||||
window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => {
|
||||
songInfo = extractedSongInfo;
|
||||
@ -43,26 +44,31 @@ export const setupTimeChangedListener = singleton(() => {
|
||||
|
||||
export const setupRepeatChangedListener = singleton(() => {
|
||||
const repeatObserver = new MutationObserver((mutations) => {
|
||||
|
||||
// provided by YouTube Music
|
||||
window.ipcRenderer.send(
|
||||
'repeatChanged',
|
||||
(mutations[0].target as Node & {
|
||||
__dataHost: {
|
||||
getState: () => GetState;
|
||||
(
|
||||
mutations[0].target as Node & {
|
||||
__dataHost: {
|
||||
getState: () => GetState;
|
||||
};
|
||||
}
|
||||
}).__dataHost.getState().queue.repeatMode,
|
||||
).__dataHost.getState().queue.repeatMode,
|
||||
);
|
||||
});
|
||||
repeatObserver.observe($('#right-controls .repeat')!, { attributeFilter: ['title'] });
|
||||
repeatObserver.observe($('#right-controls .repeat')!, {
|
||||
attributeFilter: ['title'],
|
||||
});
|
||||
|
||||
// Emit the initial value as well; as it's persistent between launches.
|
||||
// provided by YouTube Music
|
||||
window.ipcRenderer.send(
|
||||
'repeatChanged',
|
||||
$<HTMLElement & {
|
||||
getState: () => GetState;
|
||||
}>('ytmusic-player-bar')?.getState().queue.repeatMode,
|
||||
$<
|
||||
HTMLElement & {
|
||||
getState: () => GetState;
|
||||
}
|
||||
>('ytmusic-player-bar')?.getState().queue.repeatMode,
|
||||
);
|
||||
});
|
||||
|
||||
@ -92,7 +98,10 @@ export default (api: YoutubePlayer) => {
|
||||
});
|
||||
|
||||
const playPausedHandler = (e: Event, status: string) => {
|
||||
if (e.target instanceof HTMLVideoElement && Math.round(e.target.currentTime) > 0) {
|
||||
if (
|
||||
e.target instanceof HTMLVideoElement &&
|
||||
Math.round(e.target.currentTime) > 0
|
||||
) {
|
||||
window.ipcRenderer.send('playPaused', {
|
||||
isPaused: status === 'pause',
|
||||
elapsedSeconds: Math.floor(e.target.currentTime),
|
||||
@ -108,7 +117,11 @@ export default (api: YoutubePlayer) => {
|
||||
const waitingEvent = new Set<string>();
|
||||
// Name = "dataloaded" and abit later "dataupdated"
|
||||
api.addEventListener('videodatachange', (name: string, videoData) => {
|
||||
document.dispatchEvent(new CustomEvent<VideoDataChanged>('videodatachange', { detail: { name, videoData } }));
|
||||
document.dispatchEvent(
|
||||
new CustomEvent<VideoDataChanged>('videodatachange', {
|
||||
detail: { name, videoData },
|
||||
}),
|
||||
);
|
||||
|
||||
if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) {
|
||||
waitingEvent.delete(videoData.videoId);
|
||||
@ -117,7 +130,8 @@ export default (api: YoutubePlayer) => {
|
||||
const video = $<HTMLVideoElement>('video');
|
||||
video?.dispatchEvent(srcChangedEvent);
|
||||
|
||||
for (const status of ['playing', 'pause'] as const) { // for fix issue that pause event not fired
|
||||
for (const status of ['playing', 'pause'] as const) {
|
||||
// for fix issue that pause event not fired
|
||||
video?.addEventListener(status, playPausedHandlers[status]);
|
||||
}
|
||||
|
||||
@ -133,13 +147,17 @@ export default (api: YoutubePlayer) => {
|
||||
function sendSongInfo(videoData: VideoDataChangeValue) {
|
||||
const data = api.getPlayerResponse();
|
||||
|
||||
data.videoDetails.album = videoData?.Hd?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album.runs?.at(0)?.text;
|
||||
data.videoDetails.album =
|
||||
videoData?.Hd?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album.runs?.at(
|
||||
0,
|
||||
)?.text;
|
||||
data.videoDetails.elapsedSeconds = 0;
|
||||
data.videoDetails.isPaused = false;
|
||||
|
||||
// HACK: This is a workaround for "podcast" type video. GREAT JOB GOOGLE.
|
||||
if (data.playabilityStatus.transportControlsConfig) {
|
||||
data.videoDetails.author = data.microformat.microformatDataRenderer.pageOwnerDetails.name;
|
||||
data.videoDetails.author =
|
||||
data.microformat.microformatDataRenderer.pageOwnerDetails.name;
|
||||
}
|
||||
|
||||
window.ipcRenderer.send('video-src-changed', data);
|
||||
|
||||
@ -42,11 +42,11 @@ export const songInfo: SongInfo = {
|
||||
// Grab the native image using the src
|
||||
export const getImage = cache(
|
||||
async (src: string): Promise<Electron.NativeImage> => {
|
||||
|
||||
const result = await net.fetch(src);
|
||||
const buffer = await result.arrayBuffer();
|
||||
const output = nativeImage.createFromBuffer(Buffer.from(buffer));
|
||||
if (output.isEmpty() && !src.endsWith('.jpg') && src.includes('.jpg')) { // Fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
||||
if (output.isEmpty() && !src.endsWith('.jpg') && src.includes('.jpg')) {
|
||||
// Fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
||||
return getImage(src.slice(0, src.lastIndexOf('.jpg') + 4));
|
||||
}
|
||||
|
||||
@ -54,7 +54,10 @@ export const getImage = cache(
|
||||
},
|
||||
);
|
||||
|
||||
const handleData = async (data: GetPlayerResponse, win: Electron.BrowserWindow) => {
|
||||
const handleData = async (
|
||||
data: GetPlayerResponse,
|
||||
win: Electron.BrowserWindow,
|
||||
) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
@ -63,7 +66,8 @@ const handleData = async (data: GetPlayerResponse, win: Electron.BrowserWindow)
|
||||
if (microformat) {
|
||||
songInfo.uploadDate = microformat.uploadDate;
|
||||
songInfo.url = microformat.urlCanonical?.split('&')[0];
|
||||
songInfo.playlistId = new URL(microformat.urlCanonical).searchParams.get('list') ?? '';
|
||||
songInfo.playlistId =
|
||||
new URL(microformat.urlCanonical).searchParams.get('list') ?? '';
|
||||
// Used for options.resumeOnStart
|
||||
config.set('url', microformat.urlCanonical);
|
||||
}
|
||||
@ -108,17 +112,26 @@ const registerProvider = (win: BrowserWindow) => {
|
||||
c(songInfo, 'video-src-changed');
|
||||
}
|
||||
});
|
||||
ipcMain.on('playPaused', (_, { isPaused, elapsedSeconds }: { isPaused: boolean, elapsedSeconds: number }) => {
|
||||
songInfo.isPaused = isPaused;
|
||||
songInfo.elapsedSeconds = elapsedSeconds;
|
||||
if (handlingData) {
|
||||
return;
|
||||
}
|
||||
ipcMain.on(
|
||||
'playPaused',
|
||||
(
|
||||
_,
|
||||
{
|
||||
isPaused,
|
||||
elapsedSeconds,
|
||||
}: { isPaused: boolean; elapsedSeconds: number },
|
||||
) => {
|
||||
songInfo.isPaused = isPaused;
|
||||
songInfo.elapsedSeconds = elapsedSeconds;
|
||||
if (handlingData) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const c of callbacks) {
|
||||
c(songInfo, 'playPaused');
|
||||
}
|
||||
});
|
||||
for (const c of callbacks) {
|
||||
c(songInfo, 'playPaused');
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const suffixesToRemove = [
|
||||
@ -133,7 +146,10 @@ export function cleanupName(name: string): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
name = name.replace(/\((?:official)? ?(?:music)? ?(?:lyrics?)? ?(?:video)?\)$/i, '');
|
||||
name = name.replace(
|
||||
/\((?:official)? ?(?:music)? ?(?:lyrics?)? ?(?:video)?\)$/i,
|
||||
'',
|
||||
);
|
||||
const lowCaseName = name.toLowerCase();
|
||||
for (const suffix of suffixesToRemove) {
|
||||
if (lowCaseName.endsWith(suffix)) {
|
||||
|
||||
Reference in New Issue
Block a user