mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
* feat(ipcMain/unm): allow passing customized source * feat(utils/Player): pass settings.unmSource According to c280221a44608777a69038306a6ea8e92953fb9a, we can let users customize their desired source now. * feat(views/settings): allow configuring sources We haven't supported specifying the environment variable in YPM yet.
276 lines
7.6 KiB
JavaScript
276 lines
7.6 KiB
JavaScript
import { app, dialog, globalShortcut, ipcMain } from 'electron';
|
||
import match from '@unblockneteasemusic/server';
|
||
import { registerGlobalShortcut } from '@/electron/globalShortcut';
|
||
import cloneDeep from 'lodash/cloneDeep';
|
||
import shortcuts from '@/utils/shortcuts';
|
||
import { createMenu } from './menu';
|
||
|
||
const clc = require('cli-color');
|
||
const log = text => {
|
||
console.log(`${clc.blueBright('[ipcMain.js]')} ${text}`);
|
||
};
|
||
|
||
const client = require('discord-rich-presence')('818936529484906596');
|
||
|
||
/**
|
||
* Make data a Buffer.
|
||
*
|
||
* @param {?} data The data to convert.
|
||
* @returns {import("buffer").Buffer} The converted data.
|
||
*/
|
||
function toBuffer(data) {
|
||
if (data instanceof Buffer) {
|
||
return data;
|
||
} else {
|
||
return Buffer.from(data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the file URI from bilivideo.
|
||
*
|
||
* @param {string} url The URL to fetch.
|
||
* @returns {Promise<string>} The file URI.
|
||
*/
|
||
async function getBiliVideoFile(url) {
|
||
const axios = await import('axios').then(m => m.default);
|
||
const response = await axios.get(url, {
|
||
headers: {
|
||
Referer: 'https://www.bilibili.com/',
|
||
'User-Agent': 'okhttp/3.4.1',
|
||
},
|
||
responseType: 'arraybuffer',
|
||
});
|
||
|
||
const buffer = toBuffer(response.data);
|
||
const encodedData = buffer.toString('base64');
|
||
|
||
return `data:application/octet-stream;base64,${encodedData}`;
|
||
}
|
||
|
||
/**
|
||
* Parse the source string (`a, b`) to source list `['a', 'b']`.
|
||
*
|
||
* @param {string} sourceString The source string.
|
||
* @returns {string[]} The source list.
|
||
*/
|
||
function parseSourceStringToList(sourceString) {
|
||
return sourceString.split(',').map(s => s.trim());
|
||
}
|
||
|
||
export function initIpcMain(win, store) {
|
||
ipcMain.handle('unblock-music', async (_, track, source) => {
|
||
// 兼容 unblockneteasemusic 所使用的 api 字段
|
||
track.alias = track.alia || [];
|
||
track.duration = track.dt || 0;
|
||
track.album = track.al || [];
|
||
track.artists = track.ar || [];
|
||
|
||
const timeoutPromise = new Promise((_, reject) => {
|
||
setTimeout(() => {
|
||
reject('timeout');
|
||
}, 5000);
|
||
});
|
||
|
||
const sourceList =
|
||
typeof source === 'string' ? parseSourceStringToList(source) : null;
|
||
log(`[UNM] using source: ${sourceList || '<default>'}`);
|
||
|
||
try {
|
||
const matchedAudio = await Promise.race([
|
||
// TODO: tell users to install yt-dlp.
|
||
// we passed "null" to source, to let UNM choose the default source.
|
||
match(track.id, sourceList, track),
|
||
timeoutPromise,
|
||
]);
|
||
|
||
if (!matchedAudio || !matchedAudio.url) {
|
||
throw new Error('no such a song found');
|
||
}
|
||
|
||
// bilibili's audio file needs some special treatment
|
||
if (matchedAudio.url.includes('bilivideo.com')) {
|
||
matchedAudio.url = await getBiliVideoFile(matchedAudio.url);
|
||
}
|
||
|
||
return matchedAudio;
|
||
} catch (err) {
|
||
const errorMessage = err instanceof Error ? `${err.message}` : `${err}`;
|
||
log(`UnblockNeteaseMusic failed: ${errorMessage}`);
|
||
return null;
|
||
}
|
||
});
|
||
|
||
ipcMain.on('close', e => {
|
||
if (process.platform === 'darwin') {
|
||
win.hide();
|
||
exitAsk(e);
|
||
} else {
|
||
let closeOpt = store.get('settings.closeAppOption');
|
||
if (closeOpt === 'exit') {
|
||
win = null;
|
||
//app.quit();
|
||
app.exit(); //exit()直接关闭客户端,不会执行quit();
|
||
} else if (closeOpt === 'minimizeToTray') {
|
||
e.preventDefault();
|
||
win.hide();
|
||
} else {
|
||
exitAskWithoutMac(e);
|
||
}
|
||
}
|
||
});
|
||
|
||
ipcMain.on('minimize', () => {
|
||
win.minimize();
|
||
});
|
||
|
||
ipcMain.on('maximizeOrUnmaximize', () => {
|
||
const isMaximized = win.isMaximized();
|
||
isMaximized ? win.unmaximize() : win.maximize();
|
||
win.webContents.send('isMaximized', isMaximized);
|
||
});
|
||
|
||
ipcMain.on('settings', (event, options) => {
|
||
store.set('settings', options);
|
||
if (options.enableGlobalShortcut) {
|
||
registerGlobalShortcut(win, store);
|
||
} else {
|
||
log('unregister global shortcut');
|
||
globalShortcut.unregisterAll();
|
||
}
|
||
});
|
||
|
||
ipcMain.on('playDiscordPresence', (event, track) => {
|
||
client.updatePresence({
|
||
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
|
||
state: track.al.name,
|
||
endTimestamp: Date.now() + track.dt,
|
||
largeImageKey: 'logo',
|
||
largeImageText: 'Listening ' + track.name,
|
||
smallImageKey: 'play',
|
||
smallImageText: 'Playing',
|
||
instance: true,
|
||
});
|
||
});
|
||
|
||
ipcMain.on('pauseDiscordPresence', (event, track) => {
|
||
client.updatePresence({
|
||
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
|
||
state: track.al.name,
|
||
largeImageKey: 'logo',
|
||
largeImageText: 'YesPlayMusic',
|
||
smallImageKey: 'pause',
|
||
smallImageText: 'Pause',
|
||
instance: true,
|
||
});
|
||
});
|
||
|
||
ipcMain.on('setProxy', (event, config) => {
|
||
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
||
store.set('proxy', proxyRules);
|
||
win.webContents.session.setProxy(
|
||
{
|
||
proxyRules,
|
||
},
|
||
() => {
|
||
log('finished setProxy');
|
||
}
|
||
);
|
||
});
|
||
|
||
ipcMain.on('removeProxy', (event, arg) => {
|
||
log('removeProxy');
|
||
win.webContents.session.setProxy({});
|
||
store.set('proxy', '');
|
||
});
|
||
|
||
ipcMain.on('switchGlobalShortcutStatusTemporary', (e, status) => {
|
||
log('switchGlobalShortcutStatusTemporary');
|
||
if (status === 'disable') {
|
||
globalShortcut.unregisterAll();
|
||
} else {
|
||
registerGlobalShortcut(win, store);
|
||
}
|
||
});
|
||
|
||
ipcMain.on('updateShortcut', (e, { id, type, shortcut }) => {
|
||
log('updateShortcut');
|
||
let shortcuts = store.get('settings.shortcuts');
|
||
let newShortcut = shortcuts.find(s => s.id === id);
|
||
newShortcut[type] = shortcut;
|
||
store.set('settings.shortcuts', shortcuts);
|
||
|
||
createMenu(win, store);
|
||
globalShortcut.unregisterAll();
|
||
registerGlobalShortcut(win, store);
|
||
});
|
||
|
||
ipcMain.on('restoreDefaultShortcuts', () => {
|
||
log('restoreDefaultShortcuts');
|
||
store.set('settings.shortcuts', cloneDeep(shortcuts));
|
||
|
||
createMenu(win, store);
|
||
globalShortcut.unregisterAll();
|
||
registerGlobalShortcut(win, store);
|
||
});
|
||
|
||
const exitAsk = e => {
|
||
e.preventDefault(); //阻止默认行为
|
||
dialog
|
||
.showMessageBox({
|
||
type: 'info',
|
||
title: 'Information',
|
||
cancelId: 2,
|
||
defaultId: 0,
|
||
message: '确定要关闭吗?',
|
||
buttons: ['最小化', '直接退出'],
|
||
})
|
||
.then(result => {
|
||
if (result.response == 0) {
|
||
e.preventDefault(); //阻止默认行为
|
||
win.minimize(); //调用 最小化实例方法
|
||
} else if (result.response == 1) {
|
||
win = null;
|
||
//app.quit();
|
||
app.exit(); //exit()直接关闭客户端,不会执行quit();
|
||
}
|
||
})
|
||
.catch(err => {
|
||
log(err);
|
||
});
|
||
};
|
||
|
||
const exitAskWithoutMac = e => {
|
||
e.preventDefault(); //阻止默认行为
|
||
dialog
|
||
.showMessageBox({
|
||
type: 'info',
|
||
title: 'Information',
|
||
cancelId: 2,
|
||
defaultId: 0,
|
||
message: '确定要关闭吗?',
|
||
buttons: ['最小化到托盘', '直接退出'],
|
||
checkboxLabel: '记住我的选择',
|
||
})
|
||
.then(result => {
|
||
if (result.checkboxChecked && result.response !== 2) {
|
||
win.webContents.send(
|
||
'rememberCloseAppOption',
|
||
result.response === 0 ? 'minimizeToTray' : 'exit'
|
||
);
|
||
}
|
||
|
||
if (result.response === 0) {
|
||
e.preventDefault(); //阻止默认行为
|
||
win.hide(); //调用 最小化实例方法
|
||
} else if (result.response === 1) {
|
||
win = null;
|
||
//app.quit();
|
||
app.exit(); //exit()直接关闭客户端,不会执行quit();
|
||
}
|
||
})
|
||
.catch(err => {
|
||
log(err);
|
||
});
|
||
};
|
||
}
|