mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
This is just a workaround, and the core issue is on UNM. However, refactoring UNM is time-comsuming and also requires a lot of hard work. Therefore, we disable "migu" at this moment simply. Fixed #1713 Fixed #1711 (?)
323 lines
8.9 KiB
JavaScript
323 lines
8.9 KiB
JavaScript
import { app, dialog, globalShortcut, ipcMain } from 'electron';
|
||
import UNM from '@unblockneteasemusic/rust-napi';
|
||
import { registerGlobalShortcut } from '@/electron/globalShortcut';
|
||
import cloneDeep from 'lodash/cloneDeep';
|
||
import shortcuts from '@/utils/shortcuts';
|
||
import { createMenu } from './menu';
|
||
import { isCreateTray, isMac } from '@/utils/platform';
|
||
|
||
const clc = require('cli-color');
|
||
const log = text => {
|
||
console.log(`${clc.blueBright('[ipcMain.js]')} ${text}`);
|
||
};
|
||
|
||
const exitAsk = (e, win) => {
|
||
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, win) => {
|
||
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);
|
||
});
|
||
};
|
||
|
||
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 base64 data from bilivideo.
|
||
*
|
||
* @param {string} url The URL to fetch.
|
||
* @returns {Promise<string>} The file base64 data.
|
||
*/
|
||
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 encodedData;
|
||
}
|
||
|
||
/**
|
||
* Parse the source string (`a, b`) to source list `['a', 'b']`.
|
||
*
|
||
* @param {import("@unblockneteasemusic/rust-napi").Executor} executor
|
||
* @param {string} sourceString The source string.
|
||
* @returns {string[]} The source list.
|
||
*/
|
||
function parseSourceStringToList(executor, sourceString) {
|
||
const availableSource = executor.list();
|
||
|
||
return sourceString
|
||
.split(',')
|
||
.map(s => s.trim().toLowerCase())
|
||
.filter(s => {
|
||
const isAvailable = availableSource.includes(s);
|
||
|
||
if (!isAvailable) {
|
||
log(`This source is not one of the supported source: ${s}`);
|
||
}
|
||
|
||
return isAvailable;
|
||
});
|
||
}
|
||
|
||
export function initIpcMain(win, store, trayEventEmitter) {
|
||
// WIP: Do not enable logging as it has some issues in non-blocking I/O environment.
|
||
// UNM.enableLogging(UNM.LoggingType.ConsoleEnv);
|
||
const unmExecutor = new UNM.Executor();
|
||
|
||
ipcMain.handle(
|
||
'unblock-music',
|
||
/**
|
||
*
|
||
* @param {*} _
|
||
* @param {string | null} sourceListString
|
||
* @param {Record<string, any>} ncmTrack
|
||
* @param {UNM.Context} context
|
||
*/
|
||
async (_, sourceListString, ncmTrack, context) => {
|
||
// Formt the track input
|
||
// FIXME: Figure out the structure of Track
|
||
const song = {
|
||
id: ncmTrack.id && ncmTrack.id.toString(),
|
||
name: ncmTrack.name,
|
||
duration: ncmTrack.dt,
|
||
album: ncmTrack.al && {
|
||
id: ncmTrack.al.id && ncmTrack.al.id.toString(),
|
||
name: ncmTrack.al.name,
|
||
},
|
||
artists: ncmTrack.ar
|
||
? ncmTrack.ar.map(({ id, name }) => ({
|
||
id: id && id.toString(),
|
||
name,
|
||
}))
|
||
: [],
|
||
};
|
||
|
||
const sourceList =
|
||
typeof sourceListString === 'string'
|
||
? parseSourceStringToList(unmExecutor, sourceListString)
|
||
: ['ytdl', 'bilibili', 'pyncm', 'kugou'];
|
||
log(`[UNM] using source: ${sourceList.join(', ')}`);
|
||
log(`[UNM] using configuration: ${JSON.stringify(context)}`);
|
||
|
||
try {
|
||
// TODO: tell users to install yt-dlp.
|
||
const matchedAudio = await unmExecutor.search(
|
||
sourceList,
|
||
song,
|
||
context
|
||
);
|
||
const retrievedSong = await unmExecutor.retrieve(matchedAudio, context);
|
||
|
||
// bilibili's audio file needs some special treatment
|
||
if (retrievedSong.url.includes('bilivideo.com')) {
|
||
retrievedSong.url = await getBiliVideoFile(retrievedSong.url);
|
||
}
|
||
|
||
log(`respond with retrieve song…`);
|
||
log(JSON.stringify(matchedAudio));
|
||
return retrievedSong;
|
||
} catch (err) {
|
||
const errorMessage = err instanceof Error ? `${err.message}` : `${err}`;
|
||
log(`UnblockNeteaseMusic failed: ${errorMessage}`);
|
||
return null;
|
||
}
|
||
}
|
||
);
|
||
|
||
ipcMain.on('close', e => {
|
||
if (isMac) {
|
||
win.hide();
|
||
exitAsk(e, win);
|
||
} 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, win);
|
||
}
|
||
}
|
||
});
|
||
|
||
ipcMain.on('minimize', () => {
|
||
win.minimize();
|
||
});
|
||
|
||
ipcMain.on('maximizeOrUnmaximize', () => {
|
||
win.isMaximized() ? win.unmaximize() : win.maximize();
|
||
});
|
||
|
||
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);
|
||
});
|
||
|
||
if (isCreateTray) {
|
||
ipcMain.on('updateTrayTooltip', (_, title) => {
|
||
trayEventEmitter.emit('updateTooltip', title);
|
||
});
|
||
ipcMain.on('updateTrayPlayState', (_, isPlaying) => {
|
||
trayEventEmitter.emit('updatePlayState', isPlaying);
|
||
});
|
||
ipcMain.on('updateTrayLikeState', (_, isLiked) => {
|
||
trayEventEmitter.emit('updateLikeState', isLiked);
|
||
});
|
||
}
|
||
}
|