mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
🦄 refactor: 主进程重构
修复导航栏不及时响应窗口状态 修复 thumb 展示异常
This commit is contained in:
@@ -28,7 +28,6 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, "electron/main/index.ts"),
|
index: resolve(__dirname, "electron/main/index.ts"),
|
||||||
lyric: resolve(__dirname, "web/lyric.html"),
|
lyric: resolve(__dirname, "web/lyric.html"),
|
||||||
loading: resolve(__dirname, "web/loading.html"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -101,6 +100,7 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, "index.html"),
|
index: resolve(__dirname, "index.html"),
|
||||||
|
loading: resolve(__dirname, "web/loading/index.html"),
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
|
|||||||
@@ -1,204 +1,63 @@
|
|||||||
import { app, shell, BrowserWindow, BrowserWindowConstructorOptions } from "electron";
|
import { app, BrowserWindow } from "electron";
|
||||||
import { electronApp } from "@electron-toolkit/utils";
|
import { electronApp } from "@electron-toolkit/utils";
|
||||||
import { join } from "path";
|
|
||||||
import { release, type } from "os";
|
import { release, type } from "os";
|
||||||
import { isDev, isMac, appName, isLinux } from "./utils";
|
import { isMac } from "./utils/config";
|
||||||
import { unregisterShortcuts } from "./shortcut";
|
import { unregisterShortcuts } from "./shortcut";
|
||||||
import { initTray, MainTray } from "./tray";
|
import { initTray, MainTray } from "./tray";
|
||||||
import { initThumbar, Thumbar } from "./thumbar";
|
import { processLog } from "./logger";
|
||||||
import { type StoreType, initStore } from "./store";
|
|
||||||
import Store from "electron-store";
|
|
||||||
import initAppServer from "../server";
|
import initAppServer from "../server";
|
||||||
import initIpcMain from "./ipcMain";
|
import { initSingleLock } from "./utils/single-lock";
|
||||||
import log from "./logger";
|
import loadWindow from "./windows/load-window";
|
||||||
// icon
|
import mainWindow from "./windows/main-window";
|
||||||
import icon from "../../public/icons/favicon.png?asset";
|
import lyricWindow from "./windows/lyric-window";
|
||||||
|
import initIpc from "./ipc";
|
||||||
|
|
||||||
// 屏蔽报错
|
// 屏蔽报错
|
||||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
|
||||||
|
|
||||||
// 模拟打包
|
|
||||||
Object.defineProperty(app, "isPackaged", {
|
|
||||||
get() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 主进程
|
// 主进程
|
||||||
class MainProcess {
|
class MainProcess {
|
||||||
// 窗口
|
// 窗口
|
||||||
mainWindow: BrowserWindow | null = null;
|
mainWindow: BrowserWindow | null = null;
|
||||||
lyricWindow: BrowserWindow | null = null;
|
lyricWindow: BrowserWindow | null = null;
|
||||||
loadingWindow: BrowserWindow | null = null;
|
loadWindow: BrowserWindow | null = null;
|
||||||
// store
|
|
||||||
store: Store<StoreType> | null = null;
|
|
||||||
// 托盘
|
// 托盘
|
||||||
mainTray: MainTray | null = null;
|
mainTray: MainTray | null = null;
|
||||||
// 工具栏
|
|
||||||
thumbar: Thumbar | null = null;
|
|
||||||
// 是否退出
|
// 是否退出
|
||||||
isQuit: boolean = false;
|
isQuit: boolean = false;
|
||||||
constructor() {
|
constructor() {
|
||||||
log.info("🚀 Main process startup");
|
processLog.info("🚀 Main process startup");
|
||||||
|
// 程序单例锁
|
||||||
|
initSingleLock();
|
||||||
// 禁用 Windows 7 的 GPU 加速功能
|
// 禁用 Windows 7 的 GPU 加速功能
|
||||||
if (release().startsWith("6.1") && type() == "Windows_NT") app.disableHardwareAcceleration();
|
if (release().startsWith("6.1") && type() == "Windows_NT") app.disableHardwareAcceleration();
|
||||||
// 单例锁
|
// 监听应用事件
|
||||||
if (!app.requestSingleInstanceLock()) {
|
this.handleAppEvents();
|
||||||
log.error("❌ There is already a program running and this process is terminated");
|
// Electron 初始化完成后
|
||||||
app.quit();
|
// 某些API只有在此事件发生后才能使用
|
||||||
process.exit(0);
|
app.whenReady().then(async () => {
|
||||||
} else this.showWindow();
|
processLog.info("🚀 Application Process Startup");
|
||||||
// 准备就绪
|
|
||||||
app.on("ready", async () => {
|
|
||||||
log.info("🚀 Application Process Startup");
|
|
||||||
// 设置应用程序名称
|
// 设置应用程序名称
|
||||||
electronApp.setAppUserModelId("com.imsyy.splayer");
|
electronApp.setAppUserModelId("com.imsyy.splayer");
|
||||||
// 初始化 store
|
|
||||||
this.store = initStore();
|
|
||||||
// 启动主服务进程
|
// 启动主服务进程
|
||||||
await initAppServer();
|
await initAppServer();
|
||||||
// 启动进程
|
// 启动窗口
|
||||||
this.createLoadingWindow();
|
this.loadWindow = loadWindow.create();
|
||||||
this.createMainWindow();
|
this.mainWindow = mainWindow.create();
|
||||||
this.createLyricsWindow();
|
this.lyricWindow = lyricWindow.create();
|
||||||
this.handleAppEvents();
|
|
||||||
this.handleWindowEvents();
|
|
||||||
// 注册其他服务
|
// 注册其他服务
|
||||||
this.mainTray = initTray(this.mainWindow!, this.lyricWindow!);
|
this.mainTray = initTray(this.mainWindow!, this.lyricWindow!);
|
||||||
this.thumbar = initThumbar(this.mainWindow!);
|
// 注册 IPC 通信
|
||||||
// 注册主进程事件
|
initIpc();
|
||||||
initIpcMain(
|
|
||||||
this.mainWindow,
|
|
||||||
this.lyricWindow,
|
|
||||||
this.loadingWindow,
|
|
||||||
this.mainTray,
|
|
||||||
this.thumbar,
|
|
||||||
this.store,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 创建窗口
|
|
||||||
createWindow(options: BrowserWindowConstructorOptions = {}): BrowserWindow {
|
|
||||||
const defaultOptions: BrowserWindowConstructorOptions = {
|
|
||||||
title: appName,
|
|
||||||
width: 1280,
|
|
||||||
height: 720,
|
|
||||||
frame: false,
|
|
||||||
center: true,
|
|
||||||
// 图标
|
|
||||||
icon,
|
|
||||||
webPreferences: {
|
|
||||||
preload: join(__dirname, "../preload/index.mjs"),
|
|
||||||
// 禁用渲染器沙盒
|
|
||||||
sandbox: false,
|
|
||||||
// 禁用同源策略
|
|
||||||
webSecurity: false,
|
|
||||||
// 允许 HTTP
|
|
||||||
allowRunningInsecureContent: true,
|
|
||||||
// 禁用拼写检查
|
|
||||||
spellcheck: false,
|
|
||||||
// 启用 Node.js
|
|
||||||
nodeIntegration: true,
|
|
||||||
nodeIntegrationInWorker: true,
|
|
||||||
// 启用上下文隔离
|
|
||||||
contextIsolation: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// 合并参数
|
|
||||||
options = Object.assign(defaultOptions, options);
|
|
||||||
// 创建窗口
|
|
||||||
const win = new BrowserWindow(options);
|
|
||||||
return win;
|
|
||||||
}
|
|
||||||
// 创建主窗口
|
|
||||||
createMainWindow() {
|
|
||||||
// 窗口配置项
|
|
||||||
const options: BrowserWindowConstructorOptions = {
|
|
||||||
width: this.store?.get("window").width,
|
|
||||||
height: this.store?.get("window").height,
|
|
||||||
minHeight: 600,
|
|
||||||
minWidth: 800,
|
|
||||||
// 菜单栏
|
|
||||||
titleBarStyle: "customButtonsOnHover",
|
|
||||||
// 立即显示窗口
|
|
||||||
show: false,
|
|
||||||
};
|
|
||||||
// 初始化窗口
|
|
||||||
this.mainWindow = this.createWindow(options);
|
|
||||||
|
|
||||||
// 渲染路径
|
|
||||||
if (isDev && process.env["ELECTRON_RENDERER_URL"]) {
|
|
||||||
this.mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
|
||||||
} else {
|
|
||||||
const port = Number(import.meta.env["VITE_SERVER_PORT"] || 25884);
|
|
||||||
this.mainWindow.loadURL(`http://127.0.0.1:${port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置网络代理
|
|
||||||
if (this.store?.get("proxy")) {
|
|
||||||
this.mainWindow.webContents.session.setProxy({ proxyRules: this.store?.get("proxy") });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 窗口打开处理程序
|
|
||||||
this.mainWindow.webContents.setWindowOpenHandler((details) => {
|
|
||||||
const { url } = details;
|
|
||||||
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
||||||
shell.openExternal(url);
|
|
||||||
}
|
|
||||||
return { action: "deny" };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 创建加载窗口
|
|
||||||
createLoadingWindow() {
|
|
||||||
// 初始化窗口
|
|
||||||
this.loadingWindow = this.createWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 560,
|
|
||||||
maxWidth: 800,
|
|
||||||
maxHeight: 560,
|
|
||||||
resizable: false,
|
|
||||||
});
|
|
||||||
// 渲染路径
|
|
||||||
this.loadingWindow.loadFile(join(__dirname, "../main/web/loading.html"));
|
|
||||||
}
|
|
||||||
// 创建桌面歌词窗口
|
|
||||||
createLyricsWindow() {
|
|
||||||
// 初始化窗口
|
|
||||||
this.lyricWindow = this.createWindow({
|
|
||||||
width: this.store?.get("lyric").width || 800,
|
|
||||||
height: this.store?.get("lyric").height || 180,
|
|
||||||
minWidth: 440,
|
|
||||||
minHeight: 120,
|
|
||||||
maxWidth: 1600,
|
|
||||||
maxHeight: 300,
|
|
||||||
// 窗口位置
|
|
||||||
x: this.store?.get("lyric").x,
|
|
||||||
y: this.store?.get("lyric").y,
|
|
||||||
transparent: true,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
||||||
alwaysOnTop: true,
|
|
||||||
resizable: true,
|
|
||||||
movable: true,
|
|
||||||
// 不在任务栏显示
|
|
||||||
skipTaskbar: true,
|
|
||||||
// 窗口不能最小化
|
|
||||||
minimizable: false,
|
|
||||||
// 窗口不能最大化
|
|
||||||
maximizable: false,
|
|
||||||
// 窗口不能进入全屏状态
|
|
||||||
fullscreenable: false,
|
|
||||||
show: false,
|
|
||||||
});
|
|
||||||
// 渲染路径
|
|
||||||
this.lyricWindow.loadFile(join(__dirname, "../main/web/lyric.html"));
|
|
||||||
}
|
|
||||||
// 应用程序事件
|
// 应用程序事件
|
||||||
handleAppEvents() {
|
handleAppEvents() {
|
||||||
// 窗口被关闭时
|
// 窗口被关闭时
|
||||||
app.on("window-all-closed", () => {
|
app.on("window-all-closed", () => {
|
||||||
if (!isMac) app.quit();
|
if (!isMac) app.quit();
|
||||||
this.mainWindow = null;
|
this.mainWindow = null;
|
||||||
this.loadingWindow = null;
|
this.loadWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 应用被激活
|
// 应用被激活
|
||||||
@@ -206,19 +65,12 @@ class MainProcess {
|
|||||||
const allWindows = BrowserWindow.getAllWindows();
|
const allWindows = BrowserWindow.getAllWindows();
|
||||||
if (allWindows.length) {
|
if (allWindows.length) {
|
||||||
allWindows[0].focus();
|
allWindows[0].focus();
|
||||||
} else {
|
|
||||||
this.createMainWindow();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 新增 session
|
|
||||||
app.on("second-instance", () => {
|
|
||||||
this.showWindow();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 自定义协议
|
// 自定义协议
|
||||||
app.on("open-url", (_, url) => {
|
app.on("open-url", (_, url) => {
|
||||||
console.log("Received custom protocol URL:", url);
|
processLog.log("Received custom protocol URL:", url);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 将要退出
|
// 将要退出
|
||||||
@@ -232,82 +84,6 @@ class MainProcess {
|
|||||||
this.isQuit = true;
|
this.isQuit = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 窗口事件
|
|
||||||
handleWindowEvents() {
|
|
||||||
this.mainWindow?.on("ready-to-show", () => {
|
|
||||||
if (!this.mainWindow) return;
|
|
||||||
this.thumbar = initThumbar(this.mainWindow);
|
|
||||||
});
|
|
||||||
this.mainWindow?.on("show", () => {
|
|
||||||
// this.mainWindow?.webContents.send("lyricsScroll");
|
|
||||||
});
|
|
||||||
this.mainWindow?.on("focus", () => {
|
|
||||||
this.saveBounds();
|
|
||||||
});
|
|
||||||
// 移动、缩放、最大化、取消最大化
|
|
||||||
this.mainWindow?.on("resized", () => {
|
|
||||||
// 若处于全屏则不保存
|
|
||||||
if (this.mainWindow?.isFullScreen()) return;
|
|
||||||
this.saveBounds();
|
|
||||||
});
|
|
||||||
this.mainWindow?.on("moved", () => {
|
|
||||||
this.saveBounds();
|
|
||||||
});
|
|
||||||
this.mainWindow?.on("maximize", () => {
|
|
||||||
this.saveBounds();
|
|
||||||
});
|
|
||||||
this.mainWindow?.on("unmaximize", () => {
|
|
||||||
this.saveBounds();
|
|
||||||
})
|
|
||||||
|
|
||||||
// Linux 无法使用 resized 和 moved
|
|
||||||
if (isLinux) {
|
|
||||||
this.mainWindow?.on("resize", () => {
|
|
||||||
// 若处于全屏则不保存
|
|
||||||
if (this.mainWindow?.isFullScreen()) return;
|
|
||||||
this.saveBounds();
|
|
||||||
})
|
|
||||||
this.mainWindow?.on("move", () => {
|
|
||||||
this.saveBounds();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 歌词窗口缩放
|
|
||||||
this.lyricWindow?.on("resized", () => {
|
|
||||||
const bounds = this.lyricWindow?.getBounds();
|
|
||||||
if (bounds) {
|
|
||||||
const { width, height } = bounds;
|
|
||||||
this.store?.set("lyric", { ...this.store?.get("lyric"), width, height });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 窗口关闭
|
|
||||||
this.mainWindow?.on("close", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (this.isQuit) {
|
|
||||||
app.exit();
|
|
||||||
} else {
|
|
||||||
this.mainWindow?.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 更新窗口大小
|
|
||||||
saveBounds() {
|
|
||||||
if (this.mainWindow?.isFullScreen()) return;
|
|
||||||
const bounds: any = this.mainWindow?.getBounds();
|
|
||||||
if (bounds) {
|
|
||||||
bounds.maximized = this.mainWindow?.isMaximized();
|
|
||||||
this.store?.set("window", bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 显示窗口
|
|
||||||
showWindow() {
|
|
||||||
if (this.mainWindow) {
|
|
||||||
this.mainWindow.show();
|
|
||||||
if (this.mainWindow.isMinimized()) this.mainWindow.restore();
|
|
||||||
this.mainWindow.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MainProcess();
|
export default new MainProcess();
|
||||||
|
|||||||
27
electron/main/ipc/index.ts
Normal file
27
electron/main/ipc/index.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import initFileIpc from "./ipc-file";
|
||||||
|
import initLyricIpc from "./ipc-lyric";
|
||||||
|
import initShortcutIpc from "./ipc-shortcut";
|
||||||
|
import initStoreIpc from "./ipc-store";
|
||||||
|
import initSystemIpc from "./ipc-system";
|
||||||
|
import initThumbarIpc from "./ipc-thumbar";
|
||||||
|
import initTrayIpc from "./ipc-tray";
|
||||||
|
import initUpdateIpc from "./ipc-update";
|
||||||
|
import initWindowsIpc from "./ipc-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化全部 IPC 通信
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const initIpc = (): void => {
|
||||||
|
initSystemIpc();
|
||||||
|
initWindowsIpc();
|
||||||
|
initUpdateIpc();
|
||||||
|
initFileIpc();
|
||||||
|
initTrayIpc();
|
||||||
|
initLyricIpc();
|
||||||
|
initStoreIpc();
|
||||||
|
initThumbarIpc();
|
||||||
|
initShortcutIpc();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initIpc;
|
||||||
376
electron/main/ipc/ipc-file.ts
Normal file
376
electron/main/ipc/ipc-file.ts
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
|
||||||
|
import { basename, join, resolve } from "path";
|
||||||
|
import { access, readFile, stat, unlink, writeFile } from "fs/promises";
|
||||||
|
import { parseFile } from "music-metadata";
|
||||||
|
import { getFileID, getFileMD5, metaDataLyricsArrayToLrc } from "../utils/helper";
|
||||||
|
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
|
||||||
|
import { ipcLog } from "../logger";
|
||||||
|
import FastGlob from "fast-glob";
|
||||||
|
import { download } from "electron-dl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件相关 IPC
|
||||||
|
*/
|
||||||
|
const initFileIpc = (): void => {
|
||||||
|
// 默认文件夹
|
||||||
|
ipcMain.handle(
|
||||||
|
"get-default-dir",
|
||||||
|
(_event, type: "documents" | "downloads" | "pictures" | "music" | "videos"): string => {
|
||||||
|
return app.getPath(type);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 遍历音乐文件
|
||||||
|
ipcMain.handle("get-music-files", async (_, dirPath: string) => {
|
||||||
|
try {
|
||||||
|
// 规范化路径
|
||||||
|
const filePath = resolve(dirPath).replace(/\\/g, "/");
|
||||||
|
console.info(`📂 Fetching music files from: ${filePath}`);
|
||||||
|
// 查找指定目录下的所有音乐文件
|
||||||
|
const musicFiles = await FastGlob("**/*.{mp3,wav,flac}", { cwd: filePath });
|
||||||
|
// 解析元信息
|
||||||
|
const metadataPromises = musicFiles.map(async (file) => {
|
||||||
|
const filePath = join(dirPath, file);
|
||||||
|
// 处理元信息
|
||||||
|
const { common, format } = await parseFile(filePath);
|
||||||
|
// 获取文件大小
|
||||||
|
const { size } = await stat(filePath);
|
||||||
|
// 判断音质等级
|
||||||
|
let quality: string;
|
||||||
|
if ((format.sampleRate || 0) >= 96000 || (format.bitsPerSample || 0) > 16) {
|
||||||
|
quality = "Hi-Res";
|
||||||
|
} else if ((format.sampleRate || 0) >= 44100) {
|
||||||
|
quality = "HQ";
|
||||||
|
} else {
|
||||||
|
quality = "SQ";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: getFileID(filePath),
|
||||||
|
name: common.title || basename(filePath),
|
||||||
|
artists: common.artists?.[0] || common.artist,
|
||||||
|
album: common.album || "",
|
||||||
|
alia: common.comment?.[0]?.text || "",
|
||||||
|
duration: (format?.duration ?? 0) * 1000,
|
||||||
|
size: (size / (1024 * 1024)).toFixed(2),
|
||||||
|
path: filePath,
|
||||||
|
quality,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const metadataArray = await Promise.all(metadataPromises);
|
||||||
|
return metadataArray;
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error fetching music metadata:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取音乐元信息
|
||||||
|
ipcMain.handle("get-music-metadata", async (_, path: string) => {
|
||||||
|
try {
|
||||||
|
const filePath = resolve(path).replace(/\\/g, "/");
|
||||||
|
const { common, format } = await parseFile(filePath);
|
||||||
|
return {
|
||||||
|
// 文件名称
|
||||||
|
fileName: basename(filePath),
|
||||||
|
// 文件大小
|
||||||
|
fileSize: (await stat(filePath)).size / (1024 * 1024),
|
||||||
|
// 元信息
|
||||||
|
common,
|
||||||
|
// 歌词
|
||||||
|
lyric:
|
||||||
|
metaDataLyricsArrayToLrc(common?.lyrics?.[0]?.syncText || []) ||
|
||||||
|
common?.lyrics?.[0]?.text ||
|
||||||
|
"",
|
||||||
|
// 音质信息
|
||||||
|
format,
|
||||||
|
// md5
|
||||||
|
md5: await getFileMD5(filePath),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error fetching music metadata:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修改音乐元信息
|
||||||
|
ipcMain.handle("set-music-metadata", async (_, path: string, metadata: any) => {
|
||||||
|
try {
|
||||||
|
const { name, artist, album, alia, lyric, cover } = metadata;
|
||||||
|
// 规范化路径
|
||||||
|
const songPath = resolve(path);
|
||||||
|
const coverPath = cover ? resolve(cover) : null;
|
||||||
|
// 读取歌曲文件
|
||||||
|
const songFile = File.createFromPath(songPath);
|
||||||
|
// 读取封面文件
|
||||||
|
const songCover = coverPath ? Picture.fromPath(coverPath) : null;
|
||||||
|
// 保存元数据
|
||||||
|
Id3v2Settings.forceDefaultVersion = true;
|
||||||
|
Id3v2Settings.defaultVersion = 3;
|
||||||
|
songFile.tag.title = name || "未知曲目";
|
||||||
|
songFile.tag.performers = [artist || "未知艺术家"];
|
||||||
|
songFile.tag.album = album || "未知专辑";
|
||||||
|
songFile.tag.albumArtists = [artist || "未知艺术家"];
|
||||||
|
songFile.tag.lyrics = lyric || "";
|
||||||
|
songFile.tag.description = alia || "";
|
||||||
|
songFile.tag.comment = alia || "";
|
||||||
|
if (songCover) songFile.tag.pictures = [songCover];
|
||||||
|
// 保存元信息
|
||||||
|
songFile.save();
|
||||||
|
songFile.dispose();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error setting music metadata:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取音乐歌词
|
||||||
|
ipcMain.handle(
|
||||||
|
"get-music-lyric",
|
||||||
|
async (
|
||||||
|
_,
|
||||||
|
path: string,
|
||||||
|
): Promise<{
|
||||||
|
lyric: string;
|
||||||
|
format: "lrc" | "ttml";
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const filePath = resolve(path).replace(/\\/g, "/");
|
||||||
|
const { common } = await parseFile(filePath);
|
||||||
|
|
||||||
|
// 尝试获取同名的歌词文件
|
||||||
|
const filePathWithoutExt = filePath.replace(/\.[^.]+$/, "");
|
||||||
|
for (const ext of ["ttml", "lrc"] as const) {
|
||||||
|
const lyricPath = `${filePathWithoutExt}.${ext}`;
|
||||||
|
ipcLog.info("lyricPath", lyricPath);
|
||||||
|
try {
|
||||||
|
await access(lyricPath);
|
||||||
|
const lyric = await readFile(lyricPath, "utf-8");
|
||||||
|
if (lyric && lyric != "") return { lyric, format: ext };
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取元数据
|
||||||
|
const lyric = common?.lyrics?.[0]?.syncText;
|
||||||
|
if (lyric && lyric.length > 0) {
|
||||||
|
return { lyric: metaDataLyricsArrayToLrc(lyric), format: "lrc" };
|
||||||
|
} else if (common?.lyrics?.[0]?.text) {
|
||||||
|
return { lyric: common?.lyrics?.[0]?.text, format: "lrc" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有歌词
|
||||||
|
return { lyric: "", format: "lrc" };
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error fetching music lyric:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 获取音乐封面
|
||||||
|
ipcMain.handle(
|
||||||
|
"get-music-cover",
|
||||||
|
async (_, path: string): Promise<{ data: Buffer; format: string } | null> => {
|
||||||
|
try {
|
||||||
|
const { common } = await parseFile(path);
|
||||||
|
// 获取封面数据
|
||||||
|
const picture = common.picture?.[0];
|
||||||
|
if (picture) {
|
||||||
|
return { data: Buffer.from(picture.data), format: picture.format };
|
||||||
|
} else {
|
||||||
|
const coverFilePath = path.replace(/\.[^.]+$/, ".jpg");
|
||||||
|
try {
|
||||||
|
await access(coverFilePath);
|
||||||
|
const coverData = await readFile(coverFilePath);
|
||||||
|
return { data: coverData, format: "image/jpeg" };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error fetching music cover:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 读取本地歌词
|
||||||
|
ipcMain.handle(
|
||||||
|
"read-local-lyric",
|
||||||
|
async (_, lyricDir: string, id: number, ext: string): Promise<string> => {
|
||||||
|
const lyricPath = join(lyricDir, `${id}.${ext}`);
|
||||||
|
try {
|
||||||
|
await access(lyricPath);
|
||||||
|
const lyric = await readFile(lyricPath, "utf-8");
|
||||||
|
if (lyric) return lyric;
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
ipcMain.handle("delete-file", async (_, path: string) => {
|
||||||
|
try {
|
||||||
|
// 规范化路径
|
||||||
|
const resolvedPath = resolve(path);
|
||||||
|
// 检查文件是否存在
|
||||||
|
try {
|
||||||
|
await access(resolvedPath);
|
||||||
|
} catch {
|
||||||
|
throw new Error("❌ File not found");
|
||||||
|
}
|
||||||
|
// 删除文件
|
||||||
|
await unlink(resolvedPath);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ File delete error", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 打开文件夹
|
||||||
|
ipcMain.on("open-folder", async (_, path: string) => {
|
||||||
|
try {
|
||||||
|
// 规范化路径
|
||||||
|
const resolvedPath = resolve(path);
|
||||||
|
// 检查文件夹是否存在
|
||||||
|
try {
|
||||||
|
await access(resolvedPath);
|
||||||
|
} catch {
|
||||||
|
throw new Error("❌ Folder not found");
|
||||||
|
}
|
||||||
|
// 打开文件夹
|
||||||
|
shell.showItemInFolder(resolvedPath);
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Folder open error", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图片选择窗口
|
||||||
|
ipcMain.handle("choose-image", async () => {
|
||||||
|
try {
|
||||||
|
const { filePaths } = await dialog.showOpenDialog({
|
||||||
|
properties: ["openFile"],
|
||||||
|
filters: [{ name: "Images", extensions: ["jpg", "jpeg", "png"] }],
|
||||||
|
});
|
||||||
|
if (!filePaths || filePaths.length === 0) return null;
|
||||||
|
return filePaths[0];
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Image choose error", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路径选择窗口
|
||||||
|
ipcMain.handle("choose-path", async () => {
|
||||||
|
try {
|
||||||
|
const { filePaths } = await dialog.showOpenDialog({
|
||||||
|
title: "选择文件夹",
|
||||||
|
defaultPath: app.getPath("downloads"),
|
||||||
|
properties: ["openDirectory", "createDirectory"],
|
||||||
|
buttonLabel: "选择文件夹",
|
||||||
|
});
|
||||||
|
if (!filePaths || filePaths.length === 0) return null;
|
||||||
|
return filePaths[0];
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Path choose error", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
ipcMain.handle(
|
||||||
|
"download-file",
|
||||||
|
async (
|
||||||
|
event,
|
||||||
|
url: string,
|
||||||
|
options: {
|
||||||
|
fileName: string;
|
||||||
|
fileType: string;
|
||||||
|
path: string;
|
||||||
|
downloadMeta?: boolean;
|
||||||
|
downloadCover?: boolean;
|
||||||
|
downloadLyric?: boolean;
|
||||||
|
saveMetaFile?: boolean;
|
||||||
|
lyric?: string;
|
||||||
|
songData?: any;
|
||||||
|
} = {
|
||||||
|
fileName: "未知文件名",
|
||||||
|
fileType: "mp3",
|
||||||
|
path: app.getPath("downloads"),
|
||||||
|
},
|
||||||
|
): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
// 获取窗口
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!win) return false;
|
||||||
|
// 获取配置
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
fileType,
|
||||||
|
path,
|
||||||
|
lyric,
|
||||||
|
downloadMeta,
|
||||||
|
downloadCover,
|
||||||
|
downloadLyric,
|
||||||
|
saveMetaFile,
|
||||||
|
songData,
|
||||||
|
} = options;
|
||||||
|
// 规范化路径
|
||||||
|
const downloadPath = resolve(path);
|
||||||
|
// 检查文件夹是否存在
|
||||||
|
try {
|
||||||
|
await access(downloadPath);
|
||||||
|
} catch {
|
||||||
|
throw new Error("❌ Folder not found");
|
||||||
|
}
|
||||||
|
// 下载文件
|
||||||
|
const songDownload = await download(win, url, {
|
||||||
|
directory: downloadPath,
|
||||||
|
filename: `${fileName}.${fileType}`,
|
||||||
|
});
|
||||||
|
if (!downloadMeta || !songData?.cover) return true;
|
||||||
|
// 下载封面
|
||||||
|
const coverUrl = songData?.coverSize?.l || songData.cover;
|
||||||
|
const coverDownload = await download(win, coverUrl, {
|
||||||
|
directory: downloadPath,
|
||||||
|
filename: `${fileName}.jpg`,
|
||||||
|
});
|
||||||
|
// 读取歌曲文件
|
||||||
|
const songFile = File.createFromPath(songDownload.getSavePath());
|
||||||
|
// 生成图片信息
|
||||||
|
const songCover = Picture.fromPath(coverDownload.getSavePath());
|
||||||
|
// 保存修改后的元数据
|
||||||
|
Id3v2Settings.forceDefaultVersion = true;
|
||||||
|
Id3v2Settings.defaultVersion = 3;
|
||||||
|
songFile.tag.title = songData?.name || "未知曲目";
|
||||||
|
songFile.tag.album = songData?.album?.name || "未知专辑";
|
||||||
|
songFile.tag.performers = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
|
||||||
|
songFile.tag.albumArtists = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
|
||||||
|
if (lyric && downloadLyric) songFile.tag.lyrics = lyric;
|
||||||
|
if (songCover && downloadCover) songFile.tag.pictures = [songCover];
|
||||||
|
// 保存元信息
|
||||||
|
songFile.save();
|
||||||
|
songFile.dispose();
|
||||||
|
// 创建同名歌词文件
|
||||||
|
if (lyric && saveMetaFile && downloadLyric) {
|
||||||
|
const lrcPath = join(downloadPath, `${fileName}.lrc`);
|
||||||
|
await writeFile(lrcPath, lyric, "utf-8");
|
||||||
|
}
|
||||||
|
// 是否删除封面
|
||||||
|
if (!saveMetaFile || !downloadCover) await unlink(coverDownload.getSavePath());
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error downloading file:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initFileIpc;
|
||||||
111
electron/main/ipc/ipc-lyric.ts
Normal file
111
electron/main/ipc/ipc-lyric.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { ipcMain, screen } from "electron";
|
||||||
|
import lyricWindow from "../windows/lyric-window";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
import { isAbsolute, relative, resolve } from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 歌词相关 IPC
|
||||||
|
*/
|
||||||
|
const initLyricIpc = (): void => {
|
||||||
|
const store = useStore();
|
||||||
|
const mainWin = mainWindow.getWin();
|
||||||
|
const lyricWin = lyricWindow.getWin();
|
||||||
|
|
||||||
|
// 切换桌面歌词
|
||||||
|
ipcMain.on("change-desktop-lyric", (_event, val: boolean) => {
|
||||||
|
if (val) {
|
||||||
|
lyricWin?.show();
|
||||||
|
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
||||||
|
} else lyricWin?.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 音乐名称更改
|
||||||
|
ipcMain.on("play-song-change", (_, title) => {
|
||||||
|
if (!title) return;
|
||||||
|
lyricWin?.webContents.send("play-song-change", title);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 音乐歌词更改
|
||||||
|
ipcMain.on("play-lyric-change", (_, lyricData) => {
|
||||||
|
if (!lyricData) return;
|
||||||
|
lyricWin?.webContents.send("play-lyric-change", lyricData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取窗口位置
|
||||||
|
ipcMain.handle("get-window-bounds", () => {
|
||||||
|
return lyricWin?.getBounds();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取屏幕尺寸
|
||||||
|
ipcMain.handle("get-screen-size", () => {
|
||||||
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
||||||
|
return { width, height };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移动窗口
|
||||||
|
ipcMain.on("move-window", (_, x, y, width, height) => {
|
||||||
|
lyricWin?.setBounds({ x, y, width, height });
|
||||||
|
// 保存配置
|
||||||
|
store.set("lyric", { ...store.get("lyric"), x, y, width, height });
|
||||||
|
// 保持置顶
|
||||||
|
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新高度
|
||||||
|
ipcMain.on("update-window-height", (_, height) => {
|
||||||
|
if (!lyricWin) return;
|
||||||
|
const { width } = lyricWin.getBounds();
|
||||||
|
// 更新窗口高度
|
||||||
|
lyricWin.setBounds({ width, height });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
ipcMain.handle("get-desktop-lyric-option", () => {
|
||||||
|
return store.get("lyric");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => {
|
||||||
|
store.set("lyric", option);
|
||||||
|
// 触发窗口更新
|
||||||
|
if (callback && lyricWin) {
|
||||||
|
lyricWin.webContents.send("desktop-lyric-option-change", option);
|
||||||
|
}
|
||||||
|
mainWin?.webContents.send("desktop-lyric-option-change", option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送主程序事件
|
||||||
|
ipcMain.on("send-main-event", (_, name, val) => {
|
||||||
|
mainWin?.webContents.send(name, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭桌面歌词
|
||||||
|
ipcMain.on("closeDesktopLyric", () => {
|
||||||
|
lyricWin?.hide();
|
||||||
|
mainWin?.webContents.send("closeDesktopLyric");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 锁定/解锁桌面歌词
|
||||||
|
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
|
||||||
|
if (!lyricWin) return;
|
||||||
|
// 是否穿透
|
||||||
|
if (isLock) {
|
||||||
|
lyricWin.setIgnoreMouseEvents(true, { forward: true });
|
||||||
|
} else {
|
||||||
|
lyricWin.setIgnoreMouseEvents(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否是子文件夹
|
||||||
|
ipcMain.handle("check-if-subfolder", (_, localFilesPath: string[], selectedDir: string) => {
|
||||||
|
const resolvedSelectedDir = resolve(selectedDir);
|
||||||
|
const allPaths = localFilesPath.map((p) => resolve(p));
|
||||||
|
return allPaths.some((existingPath) => {
|
||||||
|
const relativePath = relative(existingPath, resolvedSelectedDir);
|
||||||
|
return relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initLyricIpc;
|
||||||
37
electron/main/ipc/ipc-shortcut.ts
Normal file
37
electron/main/ipc/ipc-shortcut.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "../shortcut";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化快捷键 IPC 主进程
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const initShortcutIpc = (): void => {
|
||||||
|
const mainWin = mainWindow.getWin();
|
||||||
|
|
||||||
|
// 快捷键是否被注册
|
||||||
|
ipcMain.handle("is-shortcut-registered", (_, shortcut: string) => isShortcutRegistered(shortcut));
|
||||||
|
|
||||||
|
// 注册快捷键
|
||||||
|
ipcMain.handle("register-all-shortcut", (_, allShortcuts: any): string[] | false => {
|
||||||
|
if (!mainWin || !allShortcuts) return false;
|
||||||
|
// 卸载所有快捷键
|
||||||
|
unregisterShortcuts();
|
||||||
|
// 注册快捷键
|
||||||
|
const failedShortcuts: string[] = [];
|
||||||
|
for (const key in allShortcuts) {
|
||||||
|
const shortcut = allShortcuts[key].globalShortcut;
|
||||||
|
if (!shortcut) continue;
|
||||||
|
// 快捷键回调
|
||||||
|
const callback = () => mainWin.webContents.send(key);
|
||||||
|
const isSuccess = registerShortcut(shortcut, callback);
|
||||||
|
if (!isSuccess) failedShortcuts.push(shortcut);
|
||||||
|
}
|
||||||
|
return failedShortcuts;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 卸载所有快捷键
|
||||||
|
ipcMain.on("unregister-all-shortcut", () => unregisterShortcuts());
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initShortcutIpc;
|
||||||
11
electron/main/ipc/ipc-store.ts
Normal file
11
electron/main/ipc/ipc-store.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { useStore } from "../store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 store IPC 主进程
|
||||||
|
*/
|
||||||
|
const initStoreIpc = (): void => {
|
||||||
|
const store = useStore();
|
||||||
|
if (!store) return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initStoreIpc;
|
||||||
95
electron/main/ipc/ipc-system.ts
Normal file
95
electron/main/ipc/ipc-system.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { ipcMain, net, powerSaveBlocker, session } from "electron";
|
||||||
|
import { ipcLog } from "../logger";
|
||||||
|
import { getFonts } from "font-list";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化系统 IPC 通信
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const initSystemIpc = (): void => {
|
||||||
|
const store = useStore();
|
||||||
|
const mainWin = mainWindow.getWin();
|
||||||
|
|
||||||
|
/** 阻止系统息屏 ID */
|
||||||
|
let preventId: number | null = null;
|
||||||
|
|
||||||
|
// 是否阻止系统息屏
|
||||||
|
ipcMain.on("prevent-sleep", (_event, val: boolean) => {
|
||||||
|
if (val) {
|
||||||
|
preventId = powerSaveBlocker.start("prevent-display-sleep");
|
||||||
|
ipcLog.info("⏾ System sleep prevention started");
|
||||||
|
} else {
|
||||||
|
if (preventId !== null) {
|
||||||
|
powerSaveBlocker.stop(preventId);
|
||||||
|
ipcLog.info("✅ System sleep prevention stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取系统全部字体
|
||||||
|
ipcMain.handle("get-all-fonts", async () => {
|
||||||
|
try {
|
||||||
|
const fonts = await getFonts();
|
||||||
|
return fonts;
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error(`❌ Failed to get all system fonts: ${error}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取消代理
|
||||||
|
ipcMain.on("remove-proxy", () => {
|
||||||
|
store.set("proxy", "");
|
||||||
|
mainWin?.webContents.session.setProxy({ proxyRules: "" });
|
||||||
|
ipcLog.info("✅ Remove proxy successfully");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 配置网络代理
|
||||||
|
ipcMain.on("set-proxy", (_, config) => {
|
||||||
|
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
||||||
|
store.set("proxy", proxyRules);
|
||||||
|
mainWin?.webContents.session.setProxy({ proxyRules });
|
||||||
|
ipcLog.info("✅ Set proxy successfully:", proxyRules);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 代理测试
|
||||||
|
ipcMain.handle("test-proxy", async (_, config) => {
|
||||||
|
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
||||||
|
try {
|
||||||
|
// 设置代理
|
||||||
|
const ses = session.defaultSession;
|
||||||
|
await ses.setProxy({ proxyRules });
|
||||||
|
// 测试请求
|
||||||
|
const request = net.request({ url: "https://www.baidu.com" });
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
request.on("response", (response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
ipcLog.info("✅ Proxy test successful");
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
ipcLog.error(`❌ Proxy test failed with status code: ${response.statusCode}`);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request.on("error", (error) => {
|
||||||
|
ipcLog.error("❌ Error testing proxy:", error);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
ipcLog.error("❌ Error testing proxy:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置全部设置
|
||||||
|
ipcMain.on("reset-setting", () => {
|
||||||
|
store.reset();
|
||||||
|
ipcLog.info("✅ Reset setting successfully");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initSystemIpc;
|
||||||
15
electron/main/ipc/ipc-thumbar.ts
Normal file
15
electron/main/ipc/ipc-thumbar.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { getThumbar } from "../thumbar";
|
||||||
|
|
||||||
|
const initThumbarIpc = (): void => {
|
||||||
|
// 更新工具栏
|
||||||
|
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
|
||||||
|
const thumbar = getThumbar();
|
||||||
|
if (!thumbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thumbar.updateThumbar(playStatus);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initThumbarIpc;
|
||||||
47
electron/main/ipc/ipc-tray.ts
Normal file
47
electron/main/ipc/ipc-tray.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { getMainTray } from "../tray";
|
||||||
|
import lyricWindow from "../windows/lyric-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 托盘 IPC
|
||||||
|
*/
|
||||||
|
const initTrayIpc = (): void => {
|
||||||
|
const tray = getMainTray();
|
||||||
|
const lyricWin = lyricWindow.getWin();
|
||||||
|
|
||||||
|
// 音乐播放状态更改
|
||||||
|
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
|
||||||
|
tray?.setPlayState(playStatus ? "play" : "pause");
|
||||||
|
lyricWin?.webContents.send("play-status-change", playStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 音乐名称更改
|
||||||
|
ipcMain.on("play-song-change", (_, title) => {
|
||||||
|
if (!title) return;
|
||||||
|
// 更改标题
|
||||||
|
tray?.setTitle(title);
|
||||||
|
tray?.setPlayName(title);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 播放模式切换
|
||||||
|
ipcMain.on("play-mode-change", (_, mode) => {
|
||||||
|
tray?.setPlayMode(mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 喜欢状态切换
|
||||||
|
ipcMain.on("like-status-change", (_, likeStatus: boolean) => {
|
||||||
|
tray?.setLikeState(likeStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 桌面歌词开关
|
||||||
|
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
|
||||||
|
tray?.setDesktopLyricShow(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 锁定/解锁桌面歌词
|
||||||
|
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
|
||||||
|
tray?.setDesktopLyricLock(isLock);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initTrayIpc;
|
||||||
15
electron/main/ipc/ipc-update.ts
Normal file
15
electron/main/ipc/ipc-update.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { checkUpdate, startDownloadUpdate } from "../update";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
|
||||||
|
const initUpdateIpc = () => {
|
||||||
|
const mainWin = mainWindow.getWin();
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
ipcMain.on("check-update", (_event, showTip) => checkUpdate(mainWin!, showTip));
|
||||||
|
|
||||||
|
// 开始下载更新
|
||||||
|
ipcMain.on("start-download-update", () => startDownloadUpdate());
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initUpdateIpc;
|
||||||
112
electron/main/ipc/ipc-window.ts
Normal file
112
electron/main/ipc/ipc-window.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { app, ipcMain } from "electron";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { isDev } from "../utils/config";
|
||||||
|
import { initThumbar } from "../thumbar";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
import loadWindow from "../windows/load-window";
|
||||||
|
import loginWindow from "../windows/login-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 窗口 IPC 通信
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
const initWindowsIpc = (): void => {
|
||||||
|
// 相关窗口
|
||||||
|
const mainWin = mainWindow.getWin();
|
||||||
|
const loadWin = loadWindow.getWin();
|
||||||
|
// store
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
// 当前窗口状态
|
||||||
|
ipcMain.on("win-state", (event) => {
|
||||||
|
event.returnValue = mainWin?.isMaximized();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载完成
|
||||||
|
ipcMain.on("win-loaded", () => {
|
||||||
|
if (loadWin && !loadWin.isDestroyed()) loadWin.destroy();
|
||||||
|
const isMaximized = store.get("window")?.maximized;
|
||||||
|
if (isMaximized) mainWin?.maximize();
|
||||||
|
mainWin?.show();
|
||||||
|
mainWin?.focus();
|
||||||
|
// 初始化缩略图工具栏
|
||||||
|
if (mainWin) initThumbar(mainWin);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 最小化
|
||||||
|
ipcMain.on("win-min", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
mainWin?.minimize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 最大化
|
||||||
|
ipcMain.on("win-max", () => {
|
||||||
|
mainWin?.maximize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 还原
|
||||||
|
ipcMain.on("win-restore", () => {
|
||||||
|
mainWin?.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
ipcMain.on("win-close", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
mainWin?.close();
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 隐藏
|
||||||
|
ipcMain.on("win-hide", () => {
|
||||||
|
mainWin?.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示
|
||||||
|
ipcMain.on("win-show", () => {
|
||||||
|
mainWin?.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重启
|
||||||
|
ipcMain.on("win-reload", () => {
|
||||||
|
app.quit();
|
||||||
|
app.relaunch();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示进度
|
||||||
|
ipcMain.on("set-bar", (_event, val: number | "none" | "indeterminate" | "error" | "paused") => {
|
||||||
|
switch (val) {
|
||||||
|
case "none":
|
||||||
|
mainWin?.setProgressBar(-1);
|
||||||
|
break;
|
||||||
|
case "indeterminate":
|
||||||
|
mainWin?.setProgressBar(2, { mode: "indeterminate" });
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
mainWin?.setProgressBar(1, { mode: "error" });
|
||||||
|
break;
|
||||||
|
case "paused":
|
||||||
|
mainWin?.setProgressBar(1, { mode: "paused" });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (typeof val === "number") {
|
||||||
|
mainWin?.setProgressBar(val / 100);
|
||||||
|
} else {
|
||||||
|
mainWin?.setProgressBar(-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开启控制台
|
||||||
|
ipcMain.on("open-dev-tools", () => {
|
||||||
|
mainWin?.webContents.openDevTools({
|
||||||
|
title: "SPlayer DevTools",
|
||||||
|
mode: isDev ? "right" : "detach",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开启登录窗口
|
||||||
|
ipcMain.on("open-login-web", () => loginWindow.create(mainWin!));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initWindowsIpc;
|
||||||
@@ -1,765 +0,0 @@
|
|||||||
import {
|
|
||||||
app,
|
|
||||||
ipcMain,
|
|
||||||
BrowserWindow,
|
|
||||||
powerSaveBlocker,
|
|
||||||
screen,
|
|
||||||
shell,
|
|
||||||
dialog,
|
|
||||||
net,
|
|
||||||
session,
|
|
||||||
} from "electron";
|
|
||||||
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
|
|
||||||
import { parseFile } from "music-metadata";
|
|
||||||
import { getFonts } from "font-list";
|
|
||||||
import { MainTray } from "./tray";
|
|
||||||
import { Thumbar } from "./thumbar";
|
|
||||||
import { StoreType } from "./store";
|
|
||||||
import { isDev, getFileID, getFileMD5, metaDataLyricsArrayToLrc } from "./utils";
|
|
||||||
import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "./shortcut";
|
|
||||||
import { join, basename, resolve, relative, isAbsolute } from "path";
|
|
||||||
import { download } from "electron-dl";
|
|
||||||
import { checkUpdate, startDownloadUpdate } from "./update";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import log from "../main/logger";
|
|
||||||
import Store from "electron-store";
|
|
||||||
import fg from "fast-glob";
|
|
||||||
import openLoginWin from "./loginWin";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
// 注册 ipcMain
|
|
||||||
const initIpcMain = (
|
|
||||||
win: BrowserWindow | null,
|
|
||||||
lyricWin: BrowserWindow | null,
|
|
||||||
loadingWin: BrowserWindow | null,
|
|
||||||
tray: MainTray | null,
|
|
||||||
thumbar: Thumbar | null,
|
|
||||||
store: Store<StoreType>,
|
|
||||||
) => {
|
|
||||||
initWinIpcMain(win, loadingWin, lyricWin, store);
|
|
||||||
initLyricIpcMain(lyricWin, win, store);
|
|
||||||
initTrayIpcMain(tray, win, lyricWin);
|
|
||||||
initThumbarIpcMain(thumbar);
|
|
||||||
initStoreIpcMain(store);
|
|
||||||
initOtherIpcMain(win);
|
|
||||||
};
|
|
||||||
|
|
||||||
// win
|
|
||||||
const initWinIpcMain = (
|
|
||||||
win: BrowserWindow | null,
|
|
||||||
loadingWin: BrowserWindow | null,
|
|
||||||
lyricWin: BrowserWindow | null,
|
|
||||||
store: Store<StoreType>,
|
|
||||||
) => {
|
|
||||||
let preventId: number | null = null;
|
|
||||||
|
|
||||||
// 当前窗口状态
|
|
||||||
ipcMain.on("win-state", (ev) => {
|
|
||||||
ev.returnValue = win?.isMaximized();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载完成
|
|
||||||
ipcMain.on("win-loaded", () => {
|
|
||||||
if (loadingWin && !loadingWin.isDestroyed()) loadingWin.close();
|
|
||||||
win?.show();
|
|
||||||
win?.focus();
|
|
||||||
const isMaximized = store?.get("window")?.maximized;
|
|
||||||
if (isMaximized) win?.maximize();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最小化
|
|
||||||
ipcMain.on("win-min", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
win?.minimize();
|
|
||||||
});
|
|
||||||
// 最大化
|
|
||||||
ipcMain.on("win-max", () => {
|
|
||||||
win?.maximize();
|
|
||||||
});
|
|
||||||
// 还原
|
|
||||||
ipcMain.on("win-restore", () => {
|
|
||||||
win?.restore();
|
|
||||||
});
|
|
||||||
// 关闭
|
|
||||||
ipcMain.on("win-close", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
win?.close();
|
|
||||||
app.quit();
|
|
||||||
});
|
|
||||||
// 隐藏
|
|
||||||
ipcMain.on("win-hide", () => {
|
|
||||||
win?.hide();
|
|
||||||
});
|
|
||||||
// 显示
|
|
||||||
ipcMain.on("win-show", () => {
|
|
||||||
win?.show();
|
|
||||||
});
|
|
||||||
// 重启
|
|
||||||
ipcMain.on("win-reload", () => {
|
|
||||||
app.quit();
|
|
||||||
app.relaunch();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 显示进度
|
|
||||||
ipcMain.on("set-bar", (_, val: number | "none" | "indeterminate" | "error" | "paused") => {
|
|
||||||
switch (val) {
|
|
||||||
case "none":
|
|
||||||
win?.setProgressBar(-1);
|
|
||||||
break;
|
|
||||||
case "indeterminate":
|
|
||||||
win?.setProgressBar(2, { mode: "indeterminate" });
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
win?.setProgressBar(1, { mode: "error" });
|
|
||||||
break;
|
|
||||||
case "paused":
|
|
||||||
win?.setProgressBar(1, { mode: "paused" });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (typeof val === "number") {
|
|
||||||
win?.setProgressBar(val / 100);
|
|
||||||
} else {
|
|
||||||
win?.setProgressBar(-1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 开启控制台
|
|
||||||
ipcMain.on("open-dev-tools", () => {
|
|
||||||
win?.webContents.openDevTools({
|
|
||||||
title: "SPlayer DevTools",
|
|
||||||
mode: isDev ? "right" : "detach",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取系统全部字体
|
|
||||||
ipcMain.handle("get-all-fonts", async () => {
|
|
||||||
try {
|
|
||||||
const fonts = await getFonts();
|
|
||||||
return fonts;
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`❌ Failed to get all system fonts: ${error}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 切换桌面歌词
|
|
||||||
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
|
|
||||||
if (val) {
|
|
||||||
lyricWin?.show();
|
|
||||||
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
|
||||||
} else lyricWin?.hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 是否阻止系统息屏
|
|
||||||
ipcMain.on("prevent-sleep", (_, val: boolean) => {
|
|
||||||
if (val) {
|
|
||||||
preventId = powerSaveBlocker.start("prevent-display-sleep");
|
|
||||||
log.info("⏾ System sleep prevention started");
|
|
||||||
} else {
|
|
||||||
if (preventId !== null) {
|
|
||||||
powerSaveBlocker.stop(preventId);
|
|
||||||
log.info("✅ System sleep prevention stopped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 默认文件夹
|
|
||||||
ipcMain.handle(
|
|
||||||
"get-default-dir",
|
|
||||||
(_, type: "documents" | "downloads" | "pictures" | "music" | "videos"): string => {
|
|
||||||
return app.getPath(type);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 遍历音乐文件
|
|
||||||
ipcMain.handle("get-music-files", async (_, dirPath: string) => {
|
|
||||||
try {
|
|
||||||
// 规范化路径
|
|
||||||
const filePath = resolve(dirPath).replace(/\\/g, "/");
|
|
||||||
console.info(`📂 Fetching music files from: ${filePath}`);
|
|
||||||
// 查找指定目录下的所有音乐文件
|
|
||||||
const musicFiles = await fg("**/*.{mp3,wav,flac}", { cwd: filePath });
|
|
||||||
// 解析元信息
|
|
||||||
const metadataPromises = musicFiles.map(async (file) => {
|
|
||||||
const filePath = join(dirPath, file);
|
|
||||||
// 处理元信息
|
|
||||||
const { common, format } = await parseFile(filePath);
|
|
||||||
// 获取文件大小
|
|
||||||
const { size } = await fs.stat(filePath);
|
|
||||||
// 判断音质等级
|
|
||||||
let quality: string;
|
|
||||||
if ((format.sampleRate || 0) >= 96000 || (format.bitsPerSample || 0) > 16) {
|
|
||||||
quality = "Hi-Res";
|
|
||||||
} else if ((format.sampleRate || 0) >= 44100) {
|
|
||||||
quality = "HQ";
|
|
||||||
} else {
|
|
||||||
quality = "SQ";
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: getFileID(filePath),
|
|
||||||
name: common.title || basename(filePath),
|
|
||||||
artists: common.artists?.[0] || common.artist,
|
|
||||||
album: common.album || "",
|
|
||||||
alia: common.comment?.[0]?.text || "",
|
|
||||||
duration: (format?.duration ?? 0) * 1000,
|
|
||||||
size: (size / (1024 * 1024)).toFixed(2),
|
|
||||||
path: filePath,
|
|
||||||
quality,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const metadataArray = await Promise.all(metadataPromises);
|
|
||||||
return metadataArray;
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error fetching music metadata:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取音乐元信息
|
|
||||||
ipcMain.handle("get-music-metadata", async (_, path: string) => {
|
|
||||||
try {
|
|
||||||
const filePath = resolve(path).replace(/\\/g, "/");
|
|
||||||
const { common, format } = await parseFile(filePath);
|
|
||||||
return {
|
|
||||||
// 文件名称
|
|
||||||
fileName: basename(filePath),
|
|
||||||
// 文件大小
|
|
||||||
fileSize: (await fs.stat(filePath)).size / (1024 * 1024),
|
|
||||||
// 元信息
|
|
||||||
common,
|
|
||||||
// 歌词
|
|
||||||
lyric:
|
|
||||||
metaDataLyricsArrayToLrc(common?.lyrics?.[0]?.syncText || []) ||
|
|
||||||
common?.lyrics?.[0]?.text ||
|
|
||||||
"",
|
|
||||||
// 音质信息
|
|
||||||
format,
|
|
||||||
// md5
|
|
||||||
md5: await getFileMD5(filePath),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error fetching music metadata:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取音乐歌词
|
|
||||||
ipcMain.handle(
|
|
||||||
"get-music-lyric",
|
|
||||||
async (
|
|
||||||
_,
|
|
||||||
path: string,
|
|
||||||
): Promise<{
|
|
||||||
lyric: string;
|
|
||||||
format: "lrc" | "ttml";
|
|
||||||
}> => {
|
|
||||||
try {
|
|
||||||
const filePath = resolve(path).replace(/\\/g, "/");
|
|
||||||
const { common } = await parseFile(filePath);
|
|
||||||
|
|
||||||
// 尝试获取同名的歌词文件
|
|
||||||
const filePathWithoutExt = filePath.replace(/\.[^.]+$/, "");
|
|
||||||
for (const ext of ["ttml", "lrc"] as const) {
|
|
||||||
const lyricPath = `${filePathWithoutExt}.${ext}`;
|
|
||||||
console.log("lyricPath", lyricPath);
|
|
||||||
try {
|
|
||||||
await fs.access(lyricPath);
|
|
||||||
const lyric = await fs.readFile(lyricPath, "utf-8");
|
|
||||||
if (lyric && lyric != "") return { lyric, format: ext };
|
|
||||||
} catch {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试获取元数据
|
|
||||||
const lyric = common?.lyrics?.[0]?.syncText;
|
|
||||||
if (lyric && lyric.length > 0) {
|
|
||||||
return { lyric: metaDataLyricsArrayToLrc(lyric), format: "lrc" };
|
|
||||||
} else if (common?.lyrics?.[0]?.text) {
|
|
||||||
return { lyric: common?.lyrics?.[0]?.text, format: "lrc" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 没有歌词
|
|
||||||
return { lyric: "", format: "lrc" };
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error fetching music lyric:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 获取音乐封面
|
|
||||||
ipcMain.handle(
|
|
||||||
"get-music-cover",
|
|
||||||
async (_, path: string): Promise<{ data: Buffer; format: string } | null> => {
|
|
||||||
try {
|
|
||||||
const { common } = await parseFile(path);
|
|
||||||
// 获取封面数据
|
|
||||||
const picture = common.picture?.[0];
|
|
||||||
if (picture) {
|
|
||||||
return { data: Buffer.from(picture.data), format: picture.format };
|
|
||||||
} else {
|
|
||||||
const coverFilePath = path.replace(/\.[^.]+$/, ".jpg");
|
|
||||||
try {
|
|
||||||
await fs.access(coverFilePath);
|
|
||||||
const coverData = await fs.readFile(coverFilePath);
|
|
||||||
return { data: coverData, format: "image/jpeg" };
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error fetching music cover:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 读取本地歌词
|
|
||||||
ipcMain.handle(
|
|
||||||
"read-local-lyric",
|
|
||||||
async (_, lyricDir: string, id: number, ext: string): Promise<string> => {
|
|
||||||
const lyricPath = path.join(lyricDir, `${id}.${ext}`);
|
|
||||||
try {
|
|
||||||
await fs.access(lyricPath);
|
|
||||||
const lyric = await fs.readFile(lyricPath, "utf-8");
|
|
||||||
if (lyric) return lyric;
|
|
||||||
} catch {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 删除文件
|
|
||||||
ipcMain.handle("delete-file", async (_, path: string) => {
|
|
||||||
try {
|
|
||||||
// 规范化路径
|
|
||||||
const resolvedPath = resolve(path);
|
|
||||||
// 检查文件是否存在
|
|
||||||
try {
|
|
||||||
await fs.access(resolvedPath);
|
|
||||||
} catch {
|
|
||||||
throw new Error("❌ File not found");
|
|
||||||
}
|
|
||||||
// 删除文件
|
|
||||||
await fs.unlink(resolvedPath);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ File delete error", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开文件夹
|
|
||||||
ipcMain.on("open-folder", async (_, path: string) => {
|
|
||||||
try {
|
|
||||||
// 规范化路径
|
|
||||||
const resolvedPath = resolve(path);
|
|
||||||
// 检查文件夹是否存在
|
|
||||||
try {
|
|
||||||
await fs.access(resolvedPath);
|
|
||||||
} catch {
|
|
||||||
throw new Error("❌ Folder not found");
|
|
||||||
}
|
|
||||||
// 打开文件夹
|
|
||||||
shell.showItemInFolder(resolvedPath);
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Folder open error", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 图片选择窗口
|
|
||||||
ipcMain.handle("choose-image", async () => {
|
|
||||||
try {
|
|
||||||
const { filePaths } = await dialog.showOpenDialog({
|
|
||||||
properties: ["openFile"],
|
|
||||||
filters: [{ name: "Images", extensions: ["jpg", "jpeg", "png"] }],
|
|
||||||
});
|
|
||||||
if (!filePaths || filePaths.length === 0) return null;
|
|
||||||
return filePaths[0];
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Image choose error", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 路径选择窗口
|
|
||||||
ipcMain.handle("choose-path", async () => {
|
|
||||||
try {
|
|
||||||
const { filePaths } = await dialog.showOpenDialog({
|
|
||||||
title: "选择文件夹",
|
|
||||||
defaultPath: app.getPath("downloads"),
|
|
||||||
properties: ["openDirectory", "createDirectory"],
|
|
||||||
buttonLabel: "选择文件夹",
|
|
||||||
});
|
|
||||||
if (!filePaths || filePaths.length === 0) return null;
|
|
||||||
return filePaths[0];
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Path choose error", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 修改音乐元信息
|
|
||||||
ipcMain.handle("set-music-metadata", async (_, path: string, metadata: any) => {
|
|
||||||
try {
|
|
||||||
const { name, artist, album, alia, lyric, cover } = metadata;
|
|
||||||
// 规范化路径
|
|
||||||
const songPath = resolve(path);
|
|
||||||
const coverPath = cover ? resolve(cover) : null;
|
|
||||||
// 读取歌曲文件
|
|
||||||
const songFile = File.createFromPath(songPath);
|
|
||||||
// 读取封面文件
|
|
||||||
const songCover = coverPath ? Picture.fromPath(coverPath) : null;
|
|
||||||
// 保存元数据
|
|
||||||
Id3v2Settings.forceDefaultVersion = true;
|
|
||||||
Id3v2Settings.defaultVersion = 3;
|
|
||||||
songFile.tag.title = name || "未知曲目";
|
|
||||||
songFile.tag.performers = [artist || "未知艺术家"];
|
|
||||||
songFile.tag.album = album || "未知专辑";
|
|
||||||
songFile.tag.albumArtists = [artist || "未知艺术家"];
|
|
||||||
songFile.tag.lyrics = lyric || "";
|
|
||||||
songFile.tag.description = alia || "";
|
|
||||||
songFile.tag.comment = alia || "";
|
|
||||||
if (songCover) songFile.tag.pictures = [songCover];
|
|
||||||
// 保存元信息
|
|
||||||
songFile.save();
|
|
||||||
songFile.dispose();
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error setting music metadata:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 下载文件
|
|
||||||
ipcMain.handle(
|
|
||||||
"download-file",
|
|
||||||
async (
|
|
||||||
_,
|
|
||||||
url: string,
|
|
||||||
options: {
|
|
||||||
fileName: string;
|
|
||||||
fileType: string;
|
|
||||||
path: string;
|
|
||||||
downloadMeta?: boolean;
|
|
||||||
downloadCover?: boolean;
|
|
||||||
downloadLyric?: boolean;
|
|
||||||
saveMetaFile?: boolean;
|
|
||||||
lyric?: string;
|
|
||||||
songData?: any;
|
|
||||||
} = {
|
|
||||||
fileName: "未知文件名",
|
|
||||||
fileType: "mp3",
|
|
||||||
path: app.getPath("downloads"),
|
|
||||||
},
|
|
||||||
): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
if (!win) return false;
|
|
||||||
// 获取配置
|
|
||||||
const {
|
|
||||||
fileName,
|
|
||||||
fileType,
|
|
||||||
path,
|
|
||||||
lyric,
|
|
||||||
downloadMeta,
|
|
||||||
downloadCover,
|
|
||||||
downloadLyric,
|
|
||||||
saveMetaFile,
|
|
||||||
songData,
|
|
||||||
} = options;
|
|
||||||
// 规范化路径
|
|
||||||
const downloadPath = resolve(path);
|
|
||||||
// 检查文件夹是否存在
|
|
||||||
try {
|
|
||||||
await fs.access(downloadPath);
|
|
||||||
} catch {
|
|
||||||
throw new Error("❌ Folder not found");
|
|
||||||
}
|
|
||||||
// 下载文件
|
|
||||||
const songDownload = await download(win, url, {
|
|
||||||
directory: downloadPath,
|
|
||||||
filename: `${fileName}.${fileType}`,
|
|
||||||
});
|
|
||||||
if (!downloadMeta || !songData?.cover) return true;
|
|
||||||
// 下载封面
|
|
||||||
const coverUrl = songData?.coverSize?.l || songData.cover;
|
|
||||||
const coverDownload = await download(win, coverUrl, {
|
|
||||||
directory: downloadPath,
|
|
||||||
filename: `${fileName}.jpg`,
|
|
||||||
});
|
|
||||||
// 读取歌曲文件
|
|
||||||
const songFile = File.createFromPath(songDownload.getSavePath());
|
|
||||||
// 生成图片信息
|
|
||||||
const songCover = Picture.fromPath(coverDownload.getSavePath());
|
|
||||||
// 保存修改后的元数据
|
|
||||||
Id3v2Settings.forceDefaultVersion = true;
|
|
||||||
Id3v2Settings.defaultVersion = 3;
|
|
||||||
songFile.tag.title = songData?.name || "未知曲目";
|
|
||||||
songFile.tag.album = songData?.album?.name || "未知专辑";
|
|
||||||
songFile.tag.performers = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
|
|
||||||
songFile.tag.albumArtists = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
|
|
||||||
if (lyric && downloadLyric) songFile.tag.lyrics = lyric;
|
|
||||||
if (songCover && downloadCover) songFile.tag.pictures = [songCover];
|
|
||||||
// 保存元信息
|
|
||||||
songFile.save();
|
|
||||||
songFile.dispose();
|
|
||||||
// 创建同名歌词文件
|
|
||||||
if (lyric && saveMetaFile && downloadLyric) {
|
|
||||||
const lrcPath = join(downloadPath, `${fileName}.lrc`);
|
|
||||||
await fs.writeFile(lrcPath, lyric, "utf-8");
|
|
||||||
}
|
|
||||||
// 是否删除封面
|
|
||||||
if (!saveMetaFile || !downloadCover) await fs.unlink(coverDownload.getSavePath());
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error downloading file:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 取消代理
|
|
||||||
ipcMain.on("remove-proxy", () => {
|
|
||||||
store.set("proxy", "");
|
|
||||||
win?.webContents.session.setProxy({ proxyRules: "" });
|
|
||||||
log.info("✅ Remove proxy successfully");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 配置网络代理
|
|
||||||
ipcMain.on("set-proxy", (_, config) => {
|
|
||||||
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
|
||||||
store.set("proxy", proxyRules);
|
|
||||||
win?.webContents.session.setProxy({ proxyRules });
|
|
||||||
log.info("✅ Set proxy successfully:", proxyRules);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 代理测试
|
|
||||||
ipcMain.handle("test-proxy", async (_, config) => {
|
|
||||||
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
|
|
||||||
try {
|
|
||||||
// 设置代理
|
|
||||||
const ses = session.defaultSession;
|
|
||||||
await ses.setProxy({ proxyRules });
|
|
||||||
// 测试请求
|
|
||||||
const request = net.request({ url: "https://www.baidu.com" });
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
request.on("response", (response) => {
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
log.info("✅ Proxy test successful");
|
|
||||||
resolve(true);
|
|
||||||
} else {
|
|
||||||
log.error(`❌ Proxy test failed with status code: ${response.statusCode}`);
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request.on("error", (error) => {
|
|
||||||
log.error("❌ Error testing proxy:", error);
|
|
||||||
resolve(false);
|
|
||||||
});
|
|
||||||
request.end();
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
log.error("❌ Error testing proxy:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重置全部设置
|
|
||||||
ipcMain.on("reset-setting", () => {
|
|
||||||
store.reset();
|
|
||||||
log.info("✅ Reset setting successfully");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查更新
|
|
||||||
ipcMain.on("check-update", (_, showTip) => checkUpdate(win!, showTip));
|
|
||||||
|
|
||||||
// 开始下载更新
|
|
||||||
ipcMain.on("start-download-update", () => startDownloadUpdate());
|
|
||||||
|
|
||||||
// 新建窗口
|
|
||||||
ipcMain.on("open-login-web", () => openLoginWin(win!));
|
|
||||||
};
|
|
||||||
|
|
||||||
// lyric
|
|
||||||
const initLyricIpcMain = (
|
|
||||||
lyricWin: BrowserWindow | null,
|
|
||||||
mainWin: BrowserWindow | null,
|
|
||||||
store: Store<StoreType>,
|
|
||||||
): void => {
|
|
||||||
// 音乐名称更改
|
|
||||||
ipcMain.on("play-song-change", (_, title) => {
|
|
||||||
if (!title) return;
|
|
||||||
lyricWin?.webContents.send("play-song-change", title);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 音乐歌词更改
|
|
||||||
ipcMain.on("play-lyric-change", (_, lyricData) => {
|
|
||||||
if (!lyricData) return;
|
|
||||||
lyricWin?.webContents.send("play-lyric-change", lyricData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取窗口位置
|
|
||||||
ipcMain.handle("get-window-bounds", () => {
|
|
||||||
return lyricWin?.getBounds();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取屏幕尺寸
|
|
||||||
ipcMain.handle("get-screen-size", () => {
|
|
||||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
|
||||||
return { width, height };
|
|
||||||
});
|
|
||||||
|
|
||||||
// 移动窗口
|
|
||||||
ipcMain.on("move-window", (_, x, y, width, height) => {
|
|
||||||
lyricWin?.setBounds({ x, y, width, height });
|
|
||||||
// 保存配置
|
|
||||||
store.set("lyric", { ...store.get("lyric"), x, y, width, height });
|
|
||||||
// 保持置顶
|
|
||||||
lyricWin?.setAlwaysOnTop(true, "screen-saver");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新高度
|
|
||||||
ipcMain.on("update-window-height", (_, height) => {
|
|
||||||
if (!lyricWin) return;
|
|
||||||
const { width } = lyricWin.getBounds();
|
|
||||||
// 更新窗口高度
|
|
||||||
lyricWin.setBounds({ width, height });
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取配置
|
|
||||||
ipcMain.handle("get-desktop-lyric-option", () => {
|
|
||||||
return store.get("lyric");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => {
|
|
||||||
store.set("lyric", option);
|
|
||||||
// 触发窗口更新
|
|
||||||
if (callback && lyricWin) {
|
|
||||||
lyricWin.webContents.send("desktop-lyric-option-change", option);
|
|
||||||
}
|
|
||||||
mainWin?.webContents.send("desktop-lyric-option-change", option);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 发送主程序事件
|
|
||||||
ipcMain.on("send-main-event", (_, name, val) => {
|
|
||||||
mainWin?.webContents.send(name, val);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 关闭桌面歌词
|
|
||||||
ipcMain.on("closeDesktopLyric", () => {
|
|
||||||
lyricWin?.hide();
|
|
||||||
mainWin?.webContents.send("closeDesktopLyric");
|
|
||||||
});
|
|
||||||
|
|
||||||
// 锁定/解锁桌面歌词
|
|
||||||
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
|
|
||||||
if (!lyricWin) return;
|
|
||||||
// 是否穿透
|
|
||||||
if (isLock) {
|
|
||||||
lyricWin.setIgnoreMouseEvents(true, { forward: true });
|
|
||||||
} else {
|
|
||||||
lyricWin.setIgnoreMouseEvents(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查是否是子文件夹
|
|
||||||
ipcMain.handle("check-if-subfolder", (_, localFilesPath: string[], selectedDir: string) => {
|
|
||||||
const resolvedSelectedDir = resolve(selectedDir);
|
|
||||||
const allPaths = localFilesPath.map((p) => resolve(p));
|
|
||||||
return allPaths.some((existingPath) => {
|
|
||||||
const relativePath = relative(existingPath, resolvedSelectedDir);
|
|
||||||
return relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// tray
|
|
||||||
const initTrayIpcMain = (
|
|
||||||
tray: MainTray | null,
|
|
||||||
win: BrowserWindow | null,
|
|
||||||
lyricWin: BrowserWindow | null,
|
|
||||||
): void => {
|
|
||||||
// 音乐播放状态更改
|
|
||||||
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
|
|
||||||
tray?.setPlayState(playStatus ? "play" : "pause");
|
|
||||||
lyricWin?.webContents.send("play-status-change", playStatus);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 音乐名称更改
|
|
||||||
ipcMain.on("play-song-change", (_, title) => {
|
|
||||||
if (!title) return;
|
|
||||||
// 更改标题
|
|
||||||
win?.setTitle(title);
|
|
||||||
tray?.setTitle(title);
|
|
||||||
tray?.setPlayName(title);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 播放模式切换
|
|
||||||
ipcMain.on("play-mode-change", (_, mode) => {
|
|
||||||
tray?.setPlayMode(mode);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 喜欢状态切换
|
|
||||||
ipcMain.on("like-status-change", (_, likeStatus: boolean) => {
|
|
||||||
tray?.setLikeState(likeStatus);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 桌面歌词开关
|
|
||||||
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
|
|
||||||
tray?.setDesktopLyricShow(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 锁定/解锁桌面歌词
|
|
||||||
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
|
|
||||||
tray?.setDesktopLyricLock(isLock);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// thumbar
|
|
||||||
const initThumbarIpcMain = (thumbar: Thumbar | null): void => {
|
|
||||||
if (!thumbar) return;
|
|
||||||
// 更新工具栏
|
|
||||||
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
|
|
||||||
thumbar?.updateThumbar(playStatus);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// store
|
|
||||||
const initStoreIpcMain = (store: Store<StoreType>): void => {
|
|
||||||
if (!store) return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// other
|
|
||||||
const initOtherIpcMain = (mainWin: BrowserWindow | null): void => {
|
|
||||||
// 快捷键是否被注册
|
|
||||||
ipcMain.handle("is-shortcut-registered", (_, shortcut: string) => isShortcutRegistered(shortcut));
|
|
||||||
|
|
||||||
// 注册快捷键
|
|
||||||
ipcMain.handle("register-all-shortcut", (_, allShortcuts: any): string[] | false => {
|
|
||||||
if (!mainWin || !allShortcuts) return false;
|
|
||||||
// 卸载所有快捷键
|
|
||||||
unregisterShortcuts();
|
|
||||||
// 注册快捷键
|
|
||||||
const failedShortcuts: string[] = [];
|
|
||||||
for (const key in allShortcuts) {
|
|
||||||
const shortcut = allShortcuts[key].globalShortcut;
|
|
||||||
if (!shortcut) continue;
|
|
||||||
// 快捷键回调
|
|
||||||
const callback = () => mainWin.webContents.send(key);
|
|
||||||
const isSuccess = registerShortcut(shortcut, callback);
|
|
||||||
if (!isSuccess) failedShortcuts.push(shortcut);
|
|
||||||
}
|
|
||||||
return failedShortcuts;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 卸载所有快捷键
|
|
||||||
ipcMain.on("unregister-all-shortcut", () => unregisterShortcuts());
|
|
||||||
};
|
|
||||||
|
|
||||||
export default initIpcMain;
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// 日志输出
|
|
||||||
import { join } from "path";
|
|
||||||
import { app } from "electron";
|
|
||||||
import { isDev } from "./utils";
|
|
||||||
import log from "electron-log";
|
|
||||||
|
|
||||||
// 绑定事件
|
|
||||||
Object.assign(console, log.functions);
|
|
||||||
|
|
||||||
// 日志配置
|
|
||||||
log.transports.file.level = "info";
|
|
||||||
log.transports.file.maxSize = 2 * 1024 * 1024; // 2M
|
|
||||||
if (log.transports.ipc) log.transports.ipc.level = false;
|
|
||||||
|
|
||||||
// 控制台输出
|
|
||||||
log.transports.console.useStyles = true;
|
|
||||||
|
|
||||||
// 文件输出
|
|
||||||
log.transports.file.format = "{y}-{m}-{d} {h}:{i}:{s}:{ms} {text}";
|
|
||||||
|
|
||||||
// 本地输出
|
|
||||||
if (!isDev) {
|
|
||||||
log.transports.file.resolvePathFn = () =>
|
|
||||||
join(app.getPath("documents"), "/SPlayer/SPlayer-log.txt");
|
|
||||||
} else {
|
|
||||||
log.transports.file.level = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("📃 logger initialized");
|
|
||||||
|
|
||||||
export default log;
|
|
||||||
47
electron/main/logger/index.ts
Normal file
47
electron/main/logger/index.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// 日志输出
|
||||||
|
import { existsSync, mkdirSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { app } from "electron";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
// 日志文件路径
|
||||||
|
const logDir = join(app.getPath("logs"));
|
||||||
|
|
||||||
|
// 是否存在日志目录
|
||||||
|
if (!existsSync(logDir)) mkdirSync(logDir);
|
||||||
|
|
||||||
|
// 获取日期 - YYYY-MM-DD
|
||||||
|
const dateString = new Date().toISOString().slice(0, 10);
|
||||||
|
const logFilePath = join(logDir, `${dateString}.log`);
|
||||||
|
|
||||||
|
// 配置日志系统
|
||||||
|
log.transports.console.useStyles = true; // 颜色输出
|
||||||
|
log.transports.file.level = "info"; // 仅记录 info 及以上级别
|
||||||
|
log.transports.file.resolvePathFn = (): string => logFilePath; // 日志文件路径
|
||||||
|
log.transports.file.maxSize = 2 * 1024 * 1024; // 文件最大 2MB
|
||||||
|
|
||||||
|
// 日志格式化
|
||||||
|
// log.transports.file.format = "[{y}-{m}-{d} {h}:{i}:{s}] [{level}] [{scope}] {text}";
|
||||||
|
|
||||||
|
// 绑定默认事件
|
||||||
|
const defaultLog = log.scope("default");
|
||||||
|
console.log = defaultLog.log;
|
||||||
|
console.info = defaultLog.info;
|
||||||
|
console.warn = defaultLog.warn;
|
||||||
|
console.error = defaultLog.error;
|
||||||
|
|
||||||
|
// 分作用域导出
|
||||||
|
export { defaultLog };
|
||||||
|
export const ipcLog = log.scope("ipc");
|
||||||
|
export const trayLog = log.scope("tray");
|
||||||
|
export const thumbarLog = log.scope("thumbar");
|
||||||
|
export const storeLog = log.scope("store");
|
||||||
|
export const updateLog = log.scope("update");
|
||||||
|
export const systemLog = log.scope("system");
|
||||||
|
export const configLog = log.scope("config");
|
||||||
|
export const windowsLog = log.scope("windows");
|
||||||
|
export const processLog = log.scope("process");
|
||||||
|
export const preloadLog = log.scope("preload");
|
||||||
|
export const rendererLog = log.scope("renderer");
|
||||||
|
export const shortcutLog = log.scope("shortcut");
|
||||||
|
export const serverLog = log.scope("server");
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { BrowserWindow, session } from "electron";
|
|
||||||
import icon from "../../public/icons/favicon.png?asset";
|
|
||||||
import { join } from "path";
|
|
||||||
|
|
||||||
const openLoginWin = async (mainWin: BrowserWindow) => {
|
|
||||||
let loginTimer: NodeJS.Timeout;
|
|
||||||
const loginSession = session.fromPartition("persist:login");
|
|
||||||
// 清除 Cookie
|
|
||||||
await loginSession.clearStorageData({
|
|
||||||
storages: ["cookies", "localstorage"],
|
|
||||||
});
|
|
||||||
const loginWin = new BrowserWindow({
|
|
||||||
parent: mainWin,
|
|
||||||
title: "登录网易云音乐( 若遇到无响应请关闭后重试 )",
|
|
||||||
width: 1280,
|
|
||||||
height: 800,
|
|
||||||
center: true,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
icon,
|
|
||||||
// resizable: false,
|
|
||||||
// movable: false,
|
|
||||||
// minimizable: false,
|
|
||||||
// maximizable: false,
|
|
||||||
webPreferences: {
|
|
||||||
session: loginSession,
|
|
||||||
sandbox: false,
|
|
||||||
webSecurity: false,
|
|
||||||
preload: join(__dirname, "../preload/index.mjs"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开网易云
|
|
||||||
loginWin.loadURL("https://music.163.com/#/login/");
|
|
||||||
|
|
||||||
// 阻止新窗口创建
|
|
||||||
loginWin.webContents.setWindowOpenHandler(() => {
|
|
||||||
return { action: "deny" };
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查是否登录
|
|
||||||
const checkLogin = async () => {
|
|
||||||
try {
|
|
||||||
loginWin.webContents.executeJavaScript(
|
|
||||||
"document.title = '登录网易云音乐( 若遇到无响应请关闭后重试 )'",
|
|
||||||
);
|
|
||||||
// 是否登录?判断 MUSIC_U
|
|
||||||
const MUSIC_U = await loginSession.cookies.get({
|
|
||||||
name: "MUSIC_U",
|
|
||||||
});
|
|
||||||
if (MUSIC_U && MUSIC_U?.length > 0) {
|
|
||||||
if (loginTimer) clearInterval(loginTimer);
|
|
||||||
const value = `MUSIC_U=${MUSIC_U[0].value};`;
|
|
||||||
// 发送回主进程
|
|
||||||
mainWin?.webContents.send("send-cookies", value);
|
|
||||||
loginWin.destroy();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 循环检查
|
|
||||||
loginWin.webContents.once("did-finish-load", () => {
|
|
||||||
loginWin.show();
|
|
||||||
loginTimer = setInterval(checkLogin, 1000);
|
|
||||||
loginWin.on("closed", () => {
|
|
||||||
clearInterval(loginTimer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default openLoginWin;
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { globalShortcut } from "electron";
|
import { globalShortcut } from "electron";
|
||||||
import log from "../main/logger";
|
import { shortcutLog } from "../logger";
|
||||||
|
|
||||||
// 注册快捷键并检查
|
// 注册快捷键并检查
|
||||||
export const registerShortcut = (shortcut: string, callback: () => void): boolean => {
|
export const registerShortcut = (shortcut: string, callback: () => void): boolean => {
|
||||||
try {
|
try {
|
||||||
const success = globalShortcut.register(shortcut, callback);
|
const success = globalShortcut.register(shortcut, callback);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
log.error(`❌ Failed to register shortcut: ${shortcut}`);
|
shortcutLog.error(`❌ Failed to register shortcut: ${shortcut}`);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
log.info(`✅ Shortcut registered: ${shortcut}`);
|
shortcutLog.info(`✅ Shortcut registered: ${shortcut}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`ℹ️ Error registering shortcut ${shortcut}:`, error);
|
shortcutLog.error(`ℹ️ Error registering shortcut ${shortcut}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -26,5 +26,5 @@ export const isShortcutRegistered = (shortcut: string): boolean => {
|
|||||||
// 卸载所有快捷键
|
// 卸载所有快捷键
|
||||||
export const unregisterShortcuts = () => {
|
export const unregisterShortcuts = () => {
|
||||||
globalShortcut.unregisterAll();
|
globalShortcut.unregisterAll();
|
||||||
log.info("🚫 All shortcuts unregistered.");
|
shortcutLog.info("🚫 All shortcuts unregistered.");
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import Store from "electron-store";
|
|
||||||
import { screen } from "electron";
|
import { screen } from "electron";
|
||||||
import log from "./logger";
|
import { storeLog } from "../logger";
|
||||||
|
import Store from "electron-store";
|
||||||
|
|
||||||
log.info("🌱 Store init");
|
storeLog.info("🌱 Store init");
|
||||||
|
|
||||||
export interface StoreType {
|
export interface StoreType {
|
||||||
window: {
|
window: {
|
||||||
@@ -25,8 +25,11 @@ export interface StoreType {
|
|||||||
proxy: string;
|
proxy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化仓库
|
/**
|
||||||
export const initStore = () => {
|
* 使用 Store
|
||||||
|
* @returns Store<StoreType>
|
||||||
|
*/
|
||||||
|
export const useStore = () => {
|
||||||
return new Store<StoreType>({
|
return new Store<StoreType>({
|
||||||
defaults: {
|
defaults: {
|
||||||
window: {
|
window: {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BrowserWindow, nativeImage, nativeTheme, ThumbarButton } from "electron";
|
import { BrowserWindow, nativeImage, nativeTheme, ThumbarButton } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { isWin } from "./utils";
|
import { isWin } from "../utils/config";
|
||||||
import log from "./logger";
|
import { thumbarLog } from "../logger";
|
||||||
|
|
||||||
enum ThumbarKeys {
|
enum ThumbarKeys {
|
||||||
Play = "play",
|
Play = "play",
|
||||||
@@ -17,6 +17,9 @@ export interface Thumbar {
|
|||||||
updateThumbar(playing: boolean, clean?: boolean): void;
|
updateThumbar(playing: boolean, clean?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 缩略图单例
|
||||||
|
let thumbar: Thumbar | null = null;
|
||||||
|
|
||||||
// 工具栏图标
|
// 工具栏图标
|
||||||
const thumbarIcon = (filename: string) => {
|
const thumbarIcon = (filename: string) => {
|
||||||
// 是否为暗色
|
// 是否为暗色
|
||||||
@@ -86,14 +89,26 @@ class createThumbar implements Thumbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化缩略图工具栏
|
||||||
|
* @param win 窗口
|
||||||
|
* @returns 缩略图工具栏
|
||||||
|
*/
|
||||||
export const initThumbar = (win: BrowserWindow) => {
|
export const initThumbar = (win: BrowserWindow) => {
|
||||||
try {
|
try {
|
||||||
// 若非 Win
|
// 若非 Win
|
||||||
if (!isWin) return null;
|
if (!isWin) return null;
|
||||||
log.info("🚀 ThumbarButtons Startup");
|
thumbarLog.info("🚀 ThumbarButtons Startup");
|
||||||
return new createThumbar(win);
|
thumbar = new createThumbar(win);
|
||||||
|
return thumbar;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("❌ ThumbarButtons Error", error);
|
thumbarLog.error("❌ ThumbarButtons Error", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缩略图工具栏
|
||||||
|
* @returns 缩略图工具栏
|
||||||
|
*/
|
||||||
|
export const getThumbar = () => thumbar;
|
||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
nativeImage,
|
nativeImage,
|
||||||
nativeTheme,
|
nativeTheme,
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { isWin, appName } from "./utils";
|
import { isWin, appName } from "../utils/config";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import log from "./logger";
|
import { trayLog } from "../logger";
|
||||||
|
|
||||||
// 播放模式
|
// 播放模式
|
||||||
type PlayMode = "repeat" | "repeat-once" | "shuffle";
|
type PlayMode = "repeat" | "repeat-once" | "shuffle";
|
||||||
@@ -34,6 +34,9 @@ export interface MainTray {
|
|||||||
destroyTray(): void;
|
destroyTray(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 托盘单例
|
||||||
|
let mainTrayInstance: MainTray | null = null;
|
||||||
|
|
||||||
// 托盘图标
|
// 托盘图标
|
||||||
const trayIcon = (filename: string) => {
|
const trayIcon = (filename: string) => {
|
||||||
// const rootPath = isDev
|
// const rootPath = isDev
|
||||||
@@ -219,11 +222,19 @@ class CreateTray implements MainTray {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 设置标题
|
// 设置标题
|
||||||
|
/**
|
||||||
|
* 设置标题
|
||||||
|
* @param title 标题
|
||||||
|
*/
|
||||||
setTitle(title: string) {
|
setTitle(title: string) {
|
||||||
|
this._win.setTitle(title);
|
||||||
this._tray.setTitle(title);
|
this._tray.setTitle(title);
|
||||||
this._tray.setToolTip(title);
|
this._tray.setToolTip(title);
|
||||||
}
|
}
|
||||||
// 设置播放名称
|
/**
|
||||||
|
* 设置播放名称
|
||||||
|
* @param name 播放名称
|
||||||
|
*/
|
||||||
setPlayName(name: string) {
|
setPlayName(name: string) {
|
||||||
// 超长处理
|
// 超长处理
|
||||||
if (name.length > 20) name = name.slice(0, 20) + "...";
|
if (name.length > 20) name = name.slice(0, 20) + "...";
|
||||||
@@ -231,48 +242,80 @@ class CreateTray implements MainTray {
|
|||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 设置播放状态
|
/**
|
||||||
|
* 设置播放状态
|
||||||
|
* @param state 播放状态
|
||||||
|
*/
|
||||||
setPlayState(state: PlayState) {
|
setPlayState(state: PlayState) {
|
||||||
playState = state;
|
playState = state;
|
||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 设置播放模式
|
/**
|
||||||
|
* 设置播放模式
|
||||||
|
* @param mode 播放模式
|
||||||
|
*/
|
||||||
setPlayMode(mode: PlayMode) {
|
setPlayMode(mode: PlayMode) {
|
||||||
playMode = mode;
|
playMode = mode;
|
||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 设置喜欢状态
|
/**
|
||||||
|
* 设置喜欢状态
|
||||||
|
* @param like 喜欢状态
|
||||||
|
*/
|
||||||
setLikeState(like: boolean) {
|
setLikeState(like: boolean) {
|
||||||
likeSong = like;
|
likeSong = like;
|
||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 桌面歌词开关
|
/**
|
||||||
|
* 桌面歌词开关
|
||||||
|
* @param show 桌面歌词开关状态
|
||||||
|
*/
|
||||||
setDesktopLyricShow(show: boolean) {
|
setDesktopLyricShow(show: boolean) {
|
||||||
desktopLyricShow = show;
|
desktopLyricShow = show;
|
||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 锁定桌面歌词
|
/**
|
||||||
|
* 锁定桌面歌词
|
||||||
|
* @param lock 锁定桌面歌词状态
|
||||||
|
*/
|
||||||
setDesktopLyricLock(lock: boolean) {
|
setDesktopLyricLock(lock: boolean) {
|
||||||
desktopLyricLock = lock;
|
desktopLyricLock = lock;
|
||||||
// 更新菜单
|
// 更新菜单
|
||||||
this.initTrayMenu();
|
this.initTrayMenu();
|
||||||
}
|
}
|
||||||
// 销毁托盘
|
/**
|
||||||
|
* 销毁托盘
|
||||||
|
*/
|
||||||
destroyTray() {
|
destroyTray() {
|
||||||
this._tray.destroy();
|
this._tray.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化托盘
|
||||||
|
* @param win 主窗口
|
||||||
|
* @param lyricWin 歌词窗口
|
||||||
|
* @returns 托盘实例
|
||||||
|
*/
|
||||||
export const initTray = (win: BrowserWindow, lyricWin: BrowserWindow) => {
|
export const initTray = (win: BrowserWindow, lyricWin: BrowserWindow) => {
|
||||||
try {
|
try {
|
||||||
log.info("🚀 Tray Process Startup");
|
trayLog.info("🚀 Tray Process Startup");
|
||||||
return new CreateTray(win, lyricWin);
|
const tray = new CreateTray(win, lyricWin);
|
||||||
|
// 保存单例实例
|
||||||
|
mainTrayInstance = tray;
|
||||||
|
return tray;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("❌ Tray Process Error", error);
|
trayLog.error("❌ Tray Process Error", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取托盘实例
|
||||||
|
* @returns 托盘实例
|
||||||
|
*/
|
||||||
|
export const getMainTray = (): MainTray | null => mainTrayInstance;
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
import { type BrowserWindow } from "electron";
|
import { app, type BrowserWindow } from "electron";
|
||||||
|
import { updateLog } from "../logger";
|
||||||
import electronUpdater from "electron-updater";
|
import electronUpdater from "electron-updater";
|
||||||
import log from "./logger";
|
import { isDev } from "../utils/config";
|
||||||
|
|
||||||
// import
|
// import
|
||||||
const { autoUpdater } = electronUpdater;
|
const { autoUpdater } = electronUpdater;
|
||||||
|
|
||||||
|
// 开发环境启用
|
||||||
|
if (isDev) {
|
||||||
|
Object.defineProperty(app, "isPackaged", {
|
||||||
|
get: () => true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 更新源
|
// 更新源
|
||||||
autoUpdater.setFeedURL({
|
autoUpdater.setFeedURL({
|
||||||
provider: "github",
|
provider: "github",
|
||||||
@@ -28,19 +36,19 @@ const initUpdaterListeners = (win: BrowserWindow) => {
|
|||||||
// 当有新版本可用时
|
// 当有新版本可用时
|
||||||
autoUpdater.on("update-available", (info) => {
|
autoUpdater.on("update-available", (info) => {
|
||||||
win.webContents.send("update-available", info);
|
win.webContents.send("update-available", info);
|
||||||
log.info(`🚀 New version available: ${info.version}`);
|
updateLog.info(`🚀 New version available: ${info.version}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新下载进度
|
// 更新下载进度
|
||||||
autoUpdater.on("download-progress", (progress) => {
|
autoUpdater.on("download-progress", (progress) => {
|
||||||
win.webContents.send("download-progress", progress);
|
win.webContents.send("download-progress", progress);
|
||||||
log.info(`🚀 Downloading: ${progress.percent}%`);
|
updateLog.info(`🚀 Downloading: ${progress.percent}%`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 当下载完成时
|
// 当下载完成时
|
||||||
autoUpdater.on("update-downloaded", (info) => {
|
autoUpdater.on("update-downloaded", (info) => {
|
||||||
win.webContents.send("update-downloaded", info);
|
win.webContents.send("update-downloaded", info);
|
||||||
log.info(`🚀 Update downloaded: ${info.version}`);
|
updateLog.info(`🚀 Update downloaded: ${info.version}`);
|
||||||
// 安装更新
|
// 安装更新
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
});
|
});
|
||||||
@@ -48,13 +56,13 @@ const initUpdaterListeners = (win: BrowserWindow) => {
|
|||||||
// 当没有新版本时
|
// 当没有新版本时
|
||||||
autoUpdater.on("update-not-available", (info) => {
|
autoUpdater.on("update-not-available", (info) => {
|
||||||
if (isShowTip) win.webContents.send("update-not-available", info);
|
if (isShowTip) win.webContents.send("update-not-available", info);
|
||||||
log.info(`✅ No new version available: ${info.version}`);
|
updateLog.info(`✅ No new version available: ${info.version}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新错误
|
// 更新错误
|
||||||
autoUpdater.on("error", (err) => {
|
autoUpdater.on("error", (err) => {
|
||||||
win.webContents.send("update-error", err);
|
win.webContents.send("update-error", err);
|
||||||
log.error(`❌ Update error: ${err.message}`);
|
updateLog.error(`❌ Update error: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
isInit = true;
|
isInit = true;
|
||||||
51
electron/main/utils/config.ts
Normal file
51
electron/main/utils/config.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { is } from "@electron-toolkit/utils";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为开发环境
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
export const isDev = is.dev;
|
||||||
|
|
||||||
|
/** 是否为 Windows 系统 */
|
||||||
|
export const isWin = process.platform === "win32";
|
||||||
|
/** 是否为 macOS 系统 */
|
||||||
|
export const isMac = process.platform === "darwin";
|
||||||
|
/** 是否为 Linux 系统 */
|
||||||
|
export const isLinux = process.platform === "linux";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软件版本
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export const appVersion = app.getVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序名称
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export const appName = app.getName() || "SPlayer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器端口
|
||||||
|
* @returns number
|
||||||
|
*/
|
||||||
|
export const port = Number(import.meta.env["VITE_SERVER_PORT"] || 25884);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主窗口加载地址
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export const mainWinUrl =
|
||||||
|
isDev && process.env["ELECTRON_RENDERER_URL"]
|
||||||
|
? process.env["ELECTRON_RENDERER_URL"]
|
||||||
|
: `http://localhost:${port}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载窗口地址
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export const loadWinUrl =
|
||||||
|
isDev && process.env["ELECTRON_RENDERER_URL"]
|
||||||
|
? `${process.env["ELECTRON_RENDERER_URL"]}/web/loading/index.html`
|
||||||
|
: `http://localhost:${port}/web/loading/index.html`;
|
||||||
@@ -1,21 +1,14 @@
|
|||||||
import { app } from "electron";
|
import { createHash } from "crypto";
|
||||||
import { is } from "@electron-toolkit/utils";
|
import { readFile } from "fs/promises";
|
||||||
import fs from "fs/promises";
|
|
||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
// 系统判断
|
/**
|
||||||
export const isDev = is.dev;
|
* 生成文件唯一ID
|
||||||
export const isWin = process.platform === "win32";
|
* @param filePath 文件路径
|
||||||
export const isMac = process.platform === "darwin";
|
* @returns 唯一ID
|
||||||
export const isLinux = process.platform === "linux";
|
*/
|
||||||
|
|
||||||
// 程序名称
|
|
||||||
export const appName = app.getName() || "SPlayer";
|
|
||||||
|
|
||||||
// 生成唯一ID
|
|
||||||
export const getFileID = (filePath: string): number => {
|
export const getFileID = (filePath: string): number => {
|
||||||
// SHA-256
|
// SHA-256
|
||||||
const hash = crypto.createHash("sha256");
|
const hash = createHash("sha256");
|
||||||
hash.update(filePath);
|
hash.update(filePath);
|
||||||
const digest = hash.digest("hex");
|
const digest = hash.digest("hex");
|
||||||
// 将哈希值的前 16 位转换为十进制数字
|
// 将哈希值的前 16 位转换为十进制数字
|
||||||
@@ -23,10 +16,14 @@ export const getFileID = (filePath: string): number => {
|
|||||||
return Number(uniqueId.toString().padStart(16, "0"));
|
return Number(uniqueId.toString().padStart(16, "0"));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成文件 MD5
|
/**
|
||||||
|
* 生成文件 MD5
|
||||||
|
* @param path 文件路径
|
||||||
|
* @returns MD5值
|
||||||
|
*/
|
||||||
export const getFileMD5 = async (path: string): Promise<string> => {
|
export const getFileMD5 = async (path: string): Promise<string> => {
|
||||||
const data = await fs.readFile(path);
|
const data = await readFile(path);
|
||||||
const hash = crypto.createHash("md5");
|
const hash = createHash("md5");
|
||||||
hash.update(data);
|
hash.update(data);
|
||||||
return hash.digest("hex");
|
return hash.digest("hex");
|
||||||
};
|
};
|
||||||
25
electron/main/utils/single-lock.ts
Normal file
25
electron/main/utils/single-lock.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { app } from "electron";
|
||||||
|
import { systemLog } from "../logger";
|
||||||
|
import mainWindow from "../windows/main-window";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化单实例锁
|
||||||
|
* @returns 如果当前实例获得了锁,返回 true;否则返回 false
|
||||||
|
*/
|
||||||
|
export const initSingleLock = (): boolean => {
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
// 如果未获得锁,退出当前实例
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit();
|
||||||
|
systemLog.warn("❌ 已有一个实例正在运行");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 当第二个实例启动时触发
|
||||||
|
else {
|
||||||
|
app.on("second-instance", () => {
|
||||||
|
systemLog.warn("❌ 第二个实例将要启动");
|
||||||
|
mainWindow.getWin()?.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
45
electron/main/windows/index.ts
Normal file
45
electron/main/windows/index.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
|
||||||
|
import { windowsLog } from "../logger";
|
||||||
|
import { appName } from "../utils/config";
|
||||||
|
import { join } from "path";
|
||||||
|
import icon from "../../../public/icons/favicon.png?asset";
|
||||||
|
|
||||||
|
export const createWindow = (
|
||||||
|
options: BrowserWindowConstructorOptions = {},
|
||||||
|
): BrowserWindow | null => {
|
||||||
|
try {
|
||||||
|
const defaultOptions: BrowserWindowConstructorOptions = {
|
||||||
|
title: appName,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
frame: false, // 创建后是否显示窗口
|
||||||
|
center: true, // 窗口居中
|
||||||
|
icon, // 窗口图标
|
||||||
|
autoHideMenuBar: true, // 隐藏菜单栏
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, "../preload/index.mjs"),
|
||||||
|
// 禁用渲染器沙盒
|
||||||
|
sandbox: false,
|
||||||
|
// 禁用同源策略
|
||||||
|
webSecurity: false,
|
||||||
|
// 允许 HTTP
|
||||||
|
allowRunningInsecureContent: true,
|
||||||
|
// 禁用拼写检查
|
||||||
|
spellcheck: false,
|
||||||
|
// 启用 Node.js
|
||||||
|
nodeIntegration: true,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
|
// 启用上下文隔离
|
||||||
|
contextIsolation: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 合并参数
|
||||||
|
options = Object.assign(defaultOptions, options);
|
||||||
|
// 创建窗口
|
||||||
|
const win = new BrowserWindow(options);
|
||||||
|
return win;
|
||||||
|
} catch (error) {
|
||||||
|
windowsLog.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
60
electron/main/windows/load-window.ts
Normal file
60
electron/main/windows/load-window.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { BrowserWindow } from "electron";
|
||||||
|
import { createWindow } from "./index";
|
||||||
|
import { loadWinUrl } from "../utils/config";
|
||||||
|
|
||||||
|
class LoadWindow {
|
||||||
|
private win: BrowserWindow | null = null;
|
||||||
|
private winURL: string;
|
||||||
|
constructor() {
|
||||||
|
this.winURL = loadWinUrl;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 主窗口事件
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
private event(): void {
|
||||||
|
if (!this.win) return;
|
||||||
|
// 准备好显示
|
||||||
|
this.win.on("ready-to-show", () => {
|
||||||
|
this.win?.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
create(): BrowserWindow | null {
|
||||||
|
this.win = createWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 560,
|
||||||
|
maxWidth: 800,
|
||||||
|
maxHeight: 560,
|
||||||
|
resizable: false,
|
||||||
|
alwaysOnTop: true,
|
||||||
|
// 不在任务栏显示
|
||||||
|
skipTaskbar: true,
|
||||||
|
// 窗口不能最小化
|
||||||
|
minimizable: false,
|
||||||
|
// 窗口不能最大化
|
||||||
|
maximizable: false,
|
||||||
|
// 窗口不能进入全屏状态
|
||||||
|
fullscreenable: false,
|
||||||
|
show: false,
|
||||||
|
});
|
||||||
|
if (!this.win) return null;
|
||||||
|
// 加载地址
|
||||||
|
this.win.loadURL(this.winURL);
|
||||||
|
// 窗口事件
|
||||||
|
this.event();
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
getWin(): BrowserWindow | null {
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LoadWindow();
|
||||||
100
electron/main/windows/login-window.ts
Normal file
100
electron/main/windows/login-window.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { app, BrowserWindow, session } from "electron";
|
||||||
|
import { createWindow } from "./index";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
class LoginWindow {
|
||||||
|
private win: BrowserWindow | null = null;
|
||||||
|
private loginTimer: NodeJS.Timeout | null = null;
|
||||||
|
private loginSession: Electron.Session | null = null;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
private getLoginSession(): Electron.Session {
|
||||||
|
if (!this.loginSession) {
|
||||||
|
this.loginSession = session.fromPartition("persist:login");
|
||||||
|
}
|
||||||
|
return this.loginSession;
|
||||||
|
}
|
||||||
|
// 事件绑定
|
||||||
|
private event(mainWin: BrowserWindow): void {
|
||||||
|
if (!this.win) return;
|
||||||
|
// 阻止新窗口创建
|
||||||
|
this.win.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
||||||
|
// 加载完成后显示并开始轮询登录状态
|
||||||
|
this.win.webContents.once("did-finish-load", () => {
|
||||||
|
this.win?.show();
|
||||||
|
this.loginTimer = setInterval(() => this.checkLogin(mainWin), 1000);
|
||||||
|
this.win?.on("closed", () => {
|
||||||
|
if (this.loginTimer) clearInterval(this.loginTimer);
|
||||||
|
this.loginTimer = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已登录
|
||||||
|
private async checkLogin(mainWin: BrowserWindow) {
|
||||||
|
if (!this.win) return;
|
||||||
|
try {
|
||||||
|
this.win.webContents.executeJavaScript(
|
||||||
|
"document.title = '登录网易云音乐( 若遇到无响应请关闭后重试 )'",
|
||||||
|
);
|
||||||
|
// 判断 MUSIC_U
|
||||||
|
const MUSIC_U = await this.getLoginSession().cookies.get({ name: "MUSIC_U" });
|
||||||
|
if (MUSIC_U && MUSIC_U.length > 0) {
|
||||||
|
if (this.loginTimer) clearInterval(this.loginTimer);
|
||||||
|
this.loginTimer = null;
|
||||||
|
const value = `MUSIC_U=${MUSIC_U[0].value};`;
|
||||||
|
// 发送回主进程
|
||||||
|
mainWin?.webContents.send("send-cookies", value);
|
||||||
|
this.win.destroy();
|
||||||
|
this.win = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建登录窗口
|
||||||
|
async create(mainWin: BrowserWindow): Promise<BrowserWindow | null> {
|
||||||
|
await app.whenReady();
|
||||||
|
const loginSession = this.getLoginSession();
|
||||||
|
// 清理登录会话存储
|
||||||
|
await loginSession.clearStorageData({
|
||||||
|
storages: ["cookies", "localstorage"],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.win = createWindow({
|
||||||
|
parent: mainWin,
|
||||||
|
title: "登录网易云音乐( 若遇到无响应请关闭后重试 )",
|
||||||
|
width: 1280,
|
||||||
|
height: 800,
|
||||||
|
center: true,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, "../preload/index.mjs"),
|
||||||
|
sandbox: false,
|
||||||
|
webSecurity: false,
|
||||||
|
allowRunningInsecureContent: true,
|
||||||
|
spellcheck: false,
|
||||||
|
nodeIntegration: true,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
session: loginSession,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.win) return null;
|
||||||
|
// 加载登录地址
|
||||||
|
this.win.loadURL("https://music.163.com/#/login/");
|
||||||
|
// 绑定事件
|
||||||
|
this.event(mainWin);
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取窗口
|
||||||
|
getWin(): BrowserWindow | null {
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LoginWindow();
|
||||||
73
electron/main/windows/lyric-window.ts
Normal file
73
electron/main/windows/lyric-window.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { BrowserWindow } from "electron";
|
||||||
|
import { createWindow } from "./index";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
class LyricWindow {
|
||||||
|
private win: BrowserWindow | null = null;
|
||||||
|
constructor() {}
|
||||||
|
/**
|
||||||
|
* 主窗口事件
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
private event(): void {
|
||||||
|
if (!this.win) return;
|
||||||
|
// 歌词窗口缩放
|
||||||
|
this.win?.on("resized", () => {
|
||||||
|
const store = useStore();
|
||||||
|
const bounds = this.win?.getBounds();
|
||||||
|
if (bounds) {
|
||||||
|
const { width, height } = bounds;
|
||||||
|
store.set("lyric", { ...store.get("lyric"), width, height });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建主窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
create(): BrowserWindow | null {
|
||||||
|
const store = useStore();
|
||||||
|
const { width, height, x, y } = store.get("lyric");
|
||||||
|
this.win = createWindow({
|
||||||
|
width: width || 800,
|
||||||
|
height: height || 180,
|
||||||
|
minWidth: 440,
|
||||||
|
minHeight: 120,
|
||||||
|
maxWidth: 1600,
|
||||||
|
maxHeight: 300,
|
||||||
|
// 窗口位置
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
transparent: true,
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0)",
|
||||||
|
alwaysOnTop: true,
|
||||||
|
resizable: true,
|
||||||
|
movable: true,
|
||||||
|
show: false,
|
||||||
|
// 不在任务栏显示
|
||||||
|
skipTaskbar: true,
|
||||||
|
// 窗口不能最小化
|
||||||
|
minimizable: false,
|
||||||
|
// 窗口不能最大化
|
||||||
|
maximizable: false,
|
||||||
|
// 窗口不能进入全屏状态
|
||||||
|
fullscreenable: false,
|
||||||
|
});
|
||||||
|
if (!this.win) return null;
|
||||||
|
// 加载地址
|
||||||
|
this.win.loadFile(join(__dirname, "../main/web/lyric.html"));
|
||||||
|
// 窗口事件
|
||||||
|
this.event();
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
getWin(): BrowserWindow | null {
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LyricWindow();
|
||||||
126
electron/main/windows/main-window.ts
Normal file
126
electron/main/windows/main-window.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { BrowserWindow, shell } from "electron";
|
||||||
|
import { createWindow } from "./index";
|
||||||
|
import { mainWinUrl } from "../utils/config";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { isLinux } from "../utils/config";
|
||||||
|
|
||||||
|
class MainWindow {
|
||||||
|
private win: BrowserWindow | null = null;
|
||||||
|
private winURL: string;
|
||||||
|
constructor() {
|
||||||
|
this.winURL = mainWinUrl;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存窗口大小和状态
|
||||||
|
*/
|
||||||
|
private saveBounds() {
|
||||||
|
if (this.win?.isFullScreen()) return;
|
||||||
|
const store = useStore();
|
||||||
|
const bounds = this.win?.getBounds();
|
||||||
|
if (bounds) {
|
||||||
|
const maximized = this.win?.isMaximized();
|
||||||
|
store.set("window", { ...bounds, maximized });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 主窗口事件
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
private event(): void {
|
||||||
|
if (!this.win) return;
|
||||||
|
const store = useStore();
|
||||||
|
// 配置网络代理
|
||||||
|
if (store.get("proxy")) {
|
||||||
|
this.win.webContents.session.setProxy({ proxyRules: store.get("proxy") });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口打开处理程序
|
||||||
|
this.win.webContents.setWindowOpenHandler((details) => {
|
||||||
|
const { url } = details;
|
||||||
|
if (url.startsWith("https://") || url.startsWith("http://")) {
|
||||||
|
shell.openExternal(url);
|
||||||
|
}
|
||||||
|
return { action: "deny" };
|
||||||
|
});
|
||||||
|
// 窗口显示时
|
||||||
|
this.win?.on("show", () => {
|
||||||
|
// this.mainWindow?.webContents.send("lyricsScroll");
|
||||||
|
});
|
||||||
|
// 窗口获得焦点时
|
||||||
|
this.win?.on("focus", () => {
|
||||||
|
this.saveBounds();
|
||||||
|
});
|
||||||
|
// 窗口大小改变时
|
||||||
|
this.win?.on("resized", () => {
|
||||||
|
// 若处于全屏则不保存
|
||||||
|
if (this.win?.isFullScreen()) return;
|
||||||
|
this.saveBounds();
|
||||||
|
});
|
||||||
|
// 窗口位置改变时
|
||||||
|
this.win?.on("moved", () => {
|
||||||
|
this.saveBounds();
|
||||||
|
});
|
||||||
|
// 窗口最大化时
|
||||||
|
this.win?.on("maximize", () => {
|
||||||
|
this.saveBounds();
|
||||||
|
this.win?.webContents.send("win-state-change", true);
|
||||||
|
});
|
||||||
|
// 窗口取消最大化时
|
||||||
|
this.win?.on("unmaximize", () => {
|
||||||
|
this.saveBounds();
|
||||||
|
this.win?.webContents.send("win-state-change", false);
|
||||||
|
});
|
||||||
|
// Linux 无法使用 resized 和 moved
|
||||||
|
if (isLinux) {
|
||||||
|
this.win?.on("resize", () => {
|
||||||
|
// 若处于全屏则不保存
|
||||||
|
if (this.win?.isFullScreen()) return;
|
||||||
|
this.saveBounds();
|
||||||
|
});
|
||||||
|
this.win?.on("move", () => {
|
||||||
|
this.saveBounds();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
create(): BrowserWindow | null {
|
||||||
|
const store = useStore();
|
||||||
|
const { width, height } = store.get("window");
|
||||||
|
this.win = createWindow({
|
||||||
|
// 菜单栏
|
||||||
|
titleBarStyle: "customButtonsOnHover",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
minHeight: 600,
|
||||||
|
minWidth: 800,
|
||||||
|
show: false,
|
||||||
|
});
|
||||||
|
if (!this.win) return null;
|
||||||
|
// 加载地址
|
||||||
|
this.win.loadURL(this.winURL);
|
||||||
|
// 窗口事件
|
||||||
|
this.event();
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取窗口
|
||||||
|
* @returns BrowserWindow | null
|
||||||
|
*/
|
||||||
|
getWin(): BrowserWindow | null {
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 显示主窗口
|
||||||
|
*/
|
||||||
|
showWindow() {
|
||||||
|
if (this.win) {
|
||||||
|
this.win.show();
|
||||||
|
if (this.win.isMinimized()) this.win.restore();
|
||||||
|
this.win.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default new MainWindow();
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { isDev } from "../main/utils";
|
import { isDev } from "../main/utils/config";
|
||||||
|
import { serverLog } from "../main/logger";
|
||||||
import initNcmAPI from "./netease";
|
import initNcmAPI from "./netease";
|
||||||
import initUnblockAPI from "./unblock";
|
import initUnblockAPI from "./unblock";
|
||||||
import fastifyCookie from "@fastify/cookie";
|
import fastifyCookie from "@fastify/cookie";
|
||||||
import fastifyMultipart from "@fastify/multipart";
|
import fastifyMultipart from "@fastify/multipart";
|
||||||
import fastifyStatic from "@fastify/static";
|
import fastifyStatic from "@fastify/static";
|
||||||
import fastify from "fastify";
|
import fastify from "fastify";
|
||||||
import log from "../main/logger";
|
|
||||||
|
|
||||||
const initAppServer = async () => {
|
const initAppServer = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -21,7 +21,7 @@ const initAppServer = async () => {
|
|||||||
server.register(fastifyMultipart);
|
server.register(fastifyMultipart);
|
||||||
// 生产环境启用静态文件
|
// 生产环境启用静态文件
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
log.info("📂 Serving static files from /renderer");
|
serverLog.info("📂 Serving static files from /renderer");
|
||||||
server.register(fastifyStatic, {
|
server.register(fastifyStatic, {
|
||||||
root: join(__dirname, "../renderer"),
|
root: join(__dirname, "../renderer"),
|
||||||
});
|
});
|
||||||
@@ -50,10 +50,10 @@ const initAppServer = async () => {
|
|||||||
// 启动端口
|
// 启动端口
|
||||||
const port = Number(process.env["VITE_SERVER_PORT"] || 25884);
|
const port = Number(process.env["VITE_SERVER_PORT"] || 25884);
|
||||||
await server.listen({ port });
|
await server.listen({ port });
|
||||||
log.info(`🌐 Starting AppServer on port ${port}`);
|
serverLog.info(`🌐 Starting AppServer on port ${port}`);
|
||||||
return server;
|
return server;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("🚫 AppServer failed to start");
|
serverLog.error("🚫 AppServer failed to start");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
||||||
import { pathCase } from "change-case";
|
import { pathCase } from "change-case";
|
||||||
|
import { serverLog } from "../../main/logger";
|
||||||
import NeteaseCloudMusicApi from "@neteasecloudmusicapienhanced/api";
|
import NeteaseCloudMusicApi from "@neteasecloudmusicapienhanced/api";
|
||||||
import log from "../../main/logger";
|
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const getHandler = (name: string, neteaseApi: (params: any) => any) => {
|
const getHandler = (name: string, neteaseApi: (params: any) => any) => {
|
||||||
@@ -9,7 +9,7 @@ const getHandler = (name: string, neteaseApi: (params: any) => any) => {
|
|||||||
req: FastifyRequest<{ Querystring: { [key: string]: string } }>,
|
req: FastifyRequest<{ Querystring: { [key: string]: string } }>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
) => {
|
) => {
|
||||||
log.info("🌐 Request NcmAPI:", name);
|
serverLog.log("🌐 Request NcmAPI:", name);
|
||||||
// 获取 NcmAPI 数据
|
// 获取 NcmAPI 数据
|
||||||
try {
|
try {
|
||||||
const result = await neteaseApi({
|
const result = await neteaseApi({
|
||||||
@@ -19,7 +19,7 @@ const getHandler = (name: string, neteaseApi: (params: any) => any) => {
|
|||||||
});
|
});
|
||||||
return reply.send(result.body);
|
return reply.send(result.body);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
log.error("❌ NcmAPI Error:", error);
|
serverLog.error("❌ NcmAPI Error:", error);
|
||||||
if ([400, 301].includes(error.status)) {
|
if ([400, 301].includes(error.status)) {
|
||||||
return reply.status(error.status).send(error.body);
|
return reply.status(error.status).send(error.body);
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ const initNcmAPI = async (fastify: FastifyInstance) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("🌐 Register NcmAPI successfully");
|
serverLog.info("🌐 Register NcmAPI successfully");
|
||||||
};
|
};
|
||||||
|
|
||||||
export default initNcmAPI;
|
export default initNcmAPI;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
||||||
import { SongUrlResult } from "./unblock";
|
import { SongUrlResult } from "./unblock";
|
||||||
|
import { serverLog } from "../../main/logger";
|
||||||
import getKuwoSongUrl from "./kuwo";
|
import getKuwoSongUrl from "./kuwo";
|
||||||
import log from "../../main/logger";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,10 +17,10 @@ const getNeteaseSongUrl = async (id: number | string): Promise<SongUrlResult> =>
|
|||||||
params: { types: "url", id },
|
params: { types: "url", id },
|
||||||
});
|
});
|
||||||
const songUrl = result.data.url;
|
const songUrl = result.data.url;
|
||||||
log.info("🔗 NeteaseSongUrl URL:", songUrl);
|
serverLog.log("🔗 NeteaseSongUrl URL:", songUrl);
|
||||||
return { code: 200, url: songUrl };
|
return { code: 200, url: songUrl };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("❌ Get NeteaseSongUrl Error:", error);
|
serverLog.error("❌ Get NeteaseSongUrl Error:", error);
|
||||||
return { code: 404, url: null };
|
return { code: 404, url: null };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -62,7 +62,7 @@ const UnblockAPI = async (fastify: FastifyInstance) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info("🌐 Register UnblockAPI successfully");
|
serverLog.info("🌐 Register UnblockAPI successfully");
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UnblockAPI;
|
export default UnblockAPI;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { encryptQuery } from "./kwDES";
|
import { encryptQuery } from "./kwDES";
|
||||||
import { SongUrlResult } from "./unblock";
|
import { SongUrlResult } from "./unblock";
|
||||||
import log from "../../main/logger";
|
import { serverLog } from "../../main/logger";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
// 获取酷我音乐歌曲 ID
|
// 获取酷我音乐歌曲 ID
|
||||||
@@ -26,7 +26,7 @@ const getKuwoSongId = async (keyword: string): Promise<string | null> => {
|
|||||||
if (songName && !songName?.includes(originalName[0])) return null;
|
if (songName && !songName?.includes(originalName[0])) return null;
|
||||||
return songId.slice("MUSIC_".length);
|
return songId.slice("MUSIC_".length);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("❌ Get KuwoSongId Error:", error);
|
serverLog.error("❌ Get KuwoSongId Error:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -53,12 +53,12 @@ const getKuwoSongUrl = async (keyword: string): Promise<SongUrlResult> => {
|
|||||||
});
|
});
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
const urlMatch = result.data.match(/http[^\s$"]+/)[0];
|
const urlMatch = result.data.match(/http[^\s$"]+/)[0];
|
||||||
log.info("🔗 KuwoSong URL:", urlMatch);
|
serverLog.log("🔗 KuwoSong URL:", urlMatch);
|
||||||
return { code: 200, url: urlMatch };
|
return { code: 200, url: urlMatch };
|
||||||
}
|
}
|
||||||
return { code: 404, url: null };
|
return { code: 404, url: null };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("❌ Get KuwoSong URL Error:", error);
|
serverLog.error("❌ Get KuwoSong URL Error:", error);
|
||||||
return { code: 404, url: null };
|
return { code: 404, url: null };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,13 +34,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@applemusic-like-lyrics/core": "^0.1.3",
|
"@applemusic-like-lyrics/core": "^0.1.3",
|
||||||
"@applemusic-like-lyrics/lyric": "^0.2.4",
|
"@applemusic-like-lyrics/lyric": "^0.3.0",
|
||||||
"@applemusic-like-lyrics/vue": "^0.1.5",
|
"@applemusic-like-lyrics/vue": "^0.1.5",
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"@imsyy/color-utils": "^1.0.2",
|
"@imsyy/color-utils": "^1.0.2",
|
||||||
"@material/material-color-utilities": "^0.3.0",
|
"@material/material-color-utilities": "^0.3.0",
|
||||||
"@neteasecloudmusicapienhanced/api": "^4.29.11",
|
"@neteasecloudmusicapienhanced/api": "^4.29.12",
|
||||||
"@pixi/app": "^7.4.3",
|
"@pixi/app": "^7.4.3",
|
||||||
"@pixi/core": "^7.4.3",
|
"@pixi/core": "^7.4.3",
|
||||||
"@pixi/display": "^7.4.3",
|
"@pixi/display": "^7.4.3",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"change-case": "^5.4.4",
|
"change-case": "^5.4.4",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"electron-dl": "^4.0.0",
|
"electron-dl": "^4.0.0",
|
||||||
"electron-store": "^8.2.0",
|
"electron-store": "^11.0.2",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"font-list": "^2.0.1",
|
"font-list": "^2.0.1",
|
||||||
|
|||||||
199
pnpm-lock.yaml
generated
199
pnpm-lock.yaml
generated
@@ -16,8 +16,8 @@ importers:
|
|||||||
specifier: ^0.1.3
|
specifier: ^0.1.3
|
||||||
version: 0.1.3(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)
|
version: 0.1.3(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)
|
||||||
'@applemusic-like-lyrics/lyric':
|
'@applemusic-like-lyrics/lyric':
|
||||||
specifier: ^0.2.4
|
specifier: ^0.3.0
|
||||||
version: 0.2.4
|
version: 0.3.0
|
||||||
'@applemusic-like-lyrics/vue':
|
'@applemusic-like-lyrics/vue':
|
||||||
specifier: ^0.1.5
|
specifier: ^0.1.5
|
||||||
version: 0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))
|
version: 0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))
|
||||||
@@ -34,8 +34,8 @@ importers:
|
|||||||
specifier: ^0.3.0
|
specifier: ^0.3.0
|
||||||
version: 0.3.0
|
version: 0.3.0
|
||||||
'@neteasecloudmusicapienhanced/api':
|
'@neteasecloudmusicapienhanced/api':
|
||||||
specifier: ^4.29.11
|
specifier: ^4.29.12
|
||||||
version: 4.29.11
|
version: 4.29.12
|
||||||
'@pixi/app':
|
'@pixi/app':
|
||||||
specifier: ^7.4.3
|
specifier: ^7.4.3
|
||||||
version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))
|
version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))
|
||||||
@@ -73,8 +73,8 @@ importers:
|
|||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
electron-store:
|
electron-store:
|
||||||
specifier: ^8.2.0
|
specifier: ^11.0.2
|
||||||
version: 8.2.0
|
version: 11.0.2
|
||||||
electron-updater:
|
electron-updater:
|
||||||
specifier: ^6.6.2
|
specifier: ^6.6.2
|
||||||
version: 6.6.2
|
version: 6.6.2
|
||||||
@@ -260,8 +260,8 @@ packages:
|
|||||||
jss: '*'
|
jss: '*'
|
||||||
jss-preset-default: '*'
|
jss-preset-default: '*'
|
||||||
|
|
||||||
'@applemusic-like-lyrics/lyric@0.2.4':
|
'@applemusic-like-lyrics/lyric@0.3.0':
|
||||||
resolution: {integrity: sha512-B1N7dMEwEIlgtyqLGVt7EbXJBQLCbv5J/N0YjkgWHu5qM1vMhKSGOXzrBuMDusmr4Z6tfO5iLfOYQUtoSXZyeg==}
|
resolution: {integrity: sha512-ZGyBheZZfqjQmGnEUciNKCARwkqIP39ONZirJE+NOQjQ47TYlZz4tlLBRH/uRfq5qviYyJ1S9Q+pZxlYoyHWVw==}
|
||||||
|
|
||||||
'@applemusic-like-lyrics/vue@0.1.5':
|
'@applemusic-like-lyrics/vue@0.1.5':
|
||||||
resolution: {integrity: sha512-FE8XtfoScmSwg61XFdNjdYBBQ8RvT10V01hFv38sMsOPUxLQn7rVUafx3NCwUDwGbAYPrC4Azn5wUpeZPdKFOg==}
|
resolution: {integrity: sha512-FE8XtfoScmSwg61XFdNjdYBBQ8RvT10V01hFv38sMsOPUxLQn7rVUafx3NCwUDwGbAYPrC4Azn5wUpeZPdKFOg==}
|
||||||
@@ -871,8 +871,8 @@ packages:
|
|||||||
'@material/material-color-utilities@0.3.0':
|
'@material/material-color-utilities@0.3.0':
|
||||||
resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==}
|
resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==}
|
||||||
|
|
||||||
'@neteasecloudmusicapienhanced/api@4.29.11':
|
'@neteasecloudmusicapienhanced/api@4.29.12':
|
||||||
resolution: {integrity: sha512-6yajC4cDH74+xpI5OadFXqIeA1TgmAi67QLRv3JbPet9JIWN6DXAagCz0QJUbo0bUPIdH1CimyU/P+eEyxP5nA==}
|
resolution: {integrity: sha512-oIT+XgjMN/m0e1C7c+KUKee9AMjAziJY28aZ2m4yAAJyMkAwgcMHpNs9nQ91U8FRHGwX4TNXoWGX1Tczx484TA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -1458,14 +1458,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
ajv-formats@2.1.1:
|
|
||||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
|
||||||
peerDependencies:
|
|
||||||
ajv: ^8.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
ajv:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
ajv-formats@3.0.1:
|
ajv-formats@3.0.1:
|
||||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1562,9 +1554,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
atomically@1.7.0:
|
atomically@2.0.3:
|
||||||
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
|
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
|
||||||
engines: {node: '>=10.12.0'}
|
|
||||||
|
|
||||||
avvio@9.1.0:
|
avvio@9.1.0:
|
||||||
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
|
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
|
||||||
@@ -1802,9 +1793,9 @@ packages:
|
|||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
conf@10.2.0:
|
conf@15.0.2:
|
||||||
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
|
resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
confbox@0.1.8:
|
confbox@0.1.8:
|
||||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||||
@@ -1909,9 +1900,9 @@ packages:
|
|||||||
dayjs@1.11.18:
|
dayjs@1.11.18:
|
||||||
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
||||||
|
|
||||||
debounce-fn@4.0.0:
|
debounce-fn@6.0.0:
|
||||||
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
|
resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
@@ -2000,9 +1991,9 @@ packages:
|
|||||||
os: [darwin]
|
os: [darwin]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
dot-prop@6.0.1:
|
dot-prop@10.1.0:
|
||||||
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
|
resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
dotenv-expand@11.0.7:
|
dotenv-expand@11.0.7:
|
||||||
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
|
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
|
||||||
@@ -2012,6 +2003,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dotenv@17.2.3:
|
||||||
|
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2052,8 +2047,9 @@ packages:
|
|||||||
electron-publish@26.0.11:
|
electron-publish@26.0.11:
|
||||||
resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==}
|
resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==}
|
||||||
|
|
||||||
electron-store@8.2.0:
|
electron-store@11.0.2:
|
||||||
resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==}
|
resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
electron-to-chromium@1.5.235:
|
electron-to-chromium@1.5.235:
|
||||||
resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==}
|
resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==}
|
||||||
@@ -2105,6 +2101,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
env-paths@3.0.0:
|
||||||
|
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
err-code@2.0.3:
|
err-code@2.0.3:
|
||||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||||
|
|
||||||
@@ -2365,10 +2365,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==}
|
resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
find-up@3.0.0:
|
|
||||||
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2715,10 +2711,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
is-obj@2.0.0:
|
|
||||||
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-plain-obj@1.1.0:
|
is-plain-obj@1.1.0:
|
||||||
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
|
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2795,8 +2787,8 @@ packages:
|
|||||||
json-schema-traverse@1.0.0:
|
json-schema-traverse@1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
json-schema-typed@7.0.3:
|
json-schema-typed@8.0.1:
|
||||||
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
|
resolution: {integrity: sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
@@ -2891,10 +2883,6 @@ packages:
|
|||||||
localforage@1.10.0:
|
localforage@1.10.0:
|
||||||
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
|
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
|
||||||
|
|
||||||
locate-path@3.0.0:
|
|
||||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3014,9 +3002,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
mimic-fn@3.1.0:
|
mimic-function@5.0.1:
|
||||||
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
|
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
mimic-response@1.0.1:
|
mimic-response@1.0.1:
|
||||||
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
|
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
|
||||||
@@ -3240,10 +3228,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
p-locate@3.0.0:
|
|
||||||
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
p-locate@4.1.0:
|
p-locate@4.1.0:
|
||||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3282,10 +3266,6 @@ packages:
|
|||||||
path-browserify@1.0.1:
|
path-browserify@1.0.1:
|
||||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
|
||||||
path-exists@3.0.0:
|
|
||||||
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
path-exists@4.0.0:
|
path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3394,10 +3374,6 @@ packages:
|
|||||||
pkg-types@2.3.0:
|
pkg-types@2.3.0:
|
||||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||||
|
|
||||||
pkg-up@3.1.0:
|
|
||||||
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
plist@3.1.0:
|
plist@3.1.0:
|
||||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||||
engines: {node: '>=10.4.0'}
|
engines: {node: '>=10.4.0'}
|
||||||
@@ -3849,6 +3825,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
|
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
stubborn-fs@1.2.5:
|
||||||
|
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
|
||||||
|
|
||||||
sumchecker@3.0.1:
|
sumchecker@3.0.1:
|
||||||
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
||||||
engines: {node: '>= 8.0'}
|
engines: {node: '>= 8.0'}
|
||||||
@@ -3869,6 +3848,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
|
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
tagged-tag@1.0.0:
|
||||||
|
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
tar@6.2.1:
|
tar@6.2.1:
|
||||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3955,9 +3938,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
type-fest@2.19.0:
|
type-fest@5.1.0:
|
||||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
resolution: {integrity: sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg==}
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
type-is@2.0.1:
|
type-is@2.0.1:
|
||||||
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
||||||
@@ -4202,6 +4185,9 @@ packages:
|
|||||||
webpack-virtual-modules@0.6.2:
|
webpack-virtual-modules@0.6.2:
|
||||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||||
|
|
||||||
|
when-exit@2.1.4:
|
||||||
|
resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==}
|
||||||
|
|
||||||
which-module@2.0.1:
|
which-module@2.0.1:
|
||||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||||
|
|
||||||
@@ -4313,7 +4299,7 @@ snapshots:
|
|||||||
jss: 10.10.0
|
jss: 10.10.0
|
||||||
jss-preset-default: 10.10.0
|
jss-preset-default: 10.10.0
|
||||||
|
|
||||||
'@applemusic-like-lyrics/lyric@0.2.4': {}
|
'@applemusic-like-lyrics/lyric@0.3.0': {}
|
||||||
|
|
||||||
'@applemusic-like-lyrics/vue@0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))':
|
'@applemusic-like-lyrics/vue@0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4943,12 +4929,12 @@ snapshots:
|
|||||||
|
|
||||||
'@material/material-color-utilities@0.3.0': {}
|
'@material/material-color-utilities@0.3.0': {}
|
||||||
|
|
||||||
'@neteasecloudmusicapienhanced/api@4.29.11':
|
'@neteasecloudmusicapienhanced/api@4.29.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unblockneteasemusic/server': 0.28.0
|
'@unblockneteasemusic/server': 0.28.0
|
||||||
axios: 1.12.2
|
axios: 1.12.2
|
||||||
crypto-js: 4.2.0
|
crypto-js: 4.2.0
|
||||||
dotenv: 16.6.1
|
dotenv: 17.2.3
|
||||||
express: 5.1.0
|
express: 5.1.0
|
||||||
express-fileupload: 1.5.2
|
express-fileupload: 1.5.2
|
||||||
md5: 2.3.0
|
md5: 2.3.0
|
||||||
@@ -5542,10 +5528,6 @@ snapshots:
|
|||||||
clean-stack: 2.2.0
|
clean-stack: 2.2.0
|
||||||
indent-string: 4.0.0
|
indent-string: 4.0.0
|
||||||
|
|
||||||
ajv-formats@2.1.1(ajv@8.17.1):
|
|
||||||
optionalDependencies:
|
|
||||||
ajv: 8.17.1
|
|
||||||
|
|
||||||
ajv-formats@3.0.1(ajv@8.17.1):
|
ajv-formats@3.0.1(ajv@8.17.1):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.17.1
|
ajv: 8.17.1
|
||||||
@@ -5663,7 +5645,10 @@ snapshots:
|
|||||||
|
|
||||||
atomic-sleep@1.0.0: {}
|
atomic-sleep@1.0.0: {}
|
||||||
|
|
||||||
atomically@1.7.0: {}
|
atomically@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
stubborn-fs: 1.2.5
|
||||||
|
when-exit: 2.1.4
|
||||||
|
|
||||||
avvio@9.1.0:
|
avvio@9.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5957,18 +5942,17 @@ snapshots:
|
|||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
conf@10.2.0:
|
conf@15.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 8.17.1
|
ajv: 8.17.1
|
||||||
ajv-formats: 2.1.1(ajv@8.17.1)
|
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||||
atomically: 1.7.0
|
atomically: 2.0.3
|
||||||
debounce-fn: 4.0.0
|
debounce-fn: 6.0.0
|
||||||
dot-prop: 6.0.1
|
dot-prop: 10.1.0
|
||||||
env-paths: 2.2.1
|
env-paths: 3.0.0
|
||||||
json-schema-typed: 7.0.3
|
json-schema-typed: 8.0.1
|
||||||
onetime: 5.1.2
|
|
||||||
pkg-up: 3.1.0
|
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
|
uint8array-extras: 1.5.0
|
||||||
|
|
||||||
confbox@0.1.8: {}
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
@@ -6058,9 +6042,9 @@ snapshots:
|
|||||||
|
|
||||||
dayjs@1.11.18: {}
|
dayjs@1.11.18: {}
|
||||||
|
|
||||||
debounce-fn@4.0.0:
|
debounce-fn@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn: 3.1.0
|
mimic-function: 5.0.1
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6154,9 +6138,9 @@ snapshots:
|
|||||||
verror: 1.10.1
|
verror: 1.10.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
dot-prop@6.0.1:
|
dot-prop@10.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-obj: 2.0.0
|
type-fest: 5.1.0
|
||||||
|
|
||||||
dotenv-expand@11.0.7:
|
dotenv-expand@11.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6164,6 +6148,8 @@ snapshots:
|
|||||||
|
|
||||||
dotenv@16.6.1: {}
|
dotenv@16.6.1: {}
|
||||||
|
|
||||||
|
dotenv@17.2.3: {}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@@ -6235,10 +6221,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
electron-store@8.2.0:
|
electron-store@11.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
conf: 10.2.0
|
conf: 15.0.2
|
||||||
type-fest: 2.19.0
|
type-fest: 5.1.0
|
||||||
|
|
||||||
electron-to-chromium@1.5.235: {}
|
electron-to-chromium@1.5.235: {}
|
||||||
|
|
||||||
@@ -6306,6 +6292,8 @@ snapshots:
|
|||||||
|
|
||||||
env-paths@2.2.1: {}
|
env-paths@2.2.1: {}
|
||||||
|
|
||||||
|
env-paths@3.0.0: {}
|
||||||
|
|
||||||
err-code@2.0.3: {}
|
err-code@2.0.3: {}
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
@@ -6652,10 +6640,6 @@ snapshots:
|
|||||||
fast-querystring: 1.1.2
|
fast-querystring: 1.1.2
|
||||||
safe-regex2: 5.0.0
|
safe-regex2: 5.0.0
|
||||||
|
|
||||||
find-up@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
locate-path: 3.0.0
|
|
||||||
|
|
||||||
find-up@4.1.0:
|
find-up@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
@@ -7016,8 +7000,6 @@ snapshots:
|
|||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
is-obj@2.0.0: {}
|
|
||||||
|
|
||||||
is-plain-obj@1.1.0: {}
|
is-plain-obj@1.1.0: {}
|
||||||
|
|
||||||
is-promise@4.0.0: {}
|
is-promise@4.0.0: {}
|
||||||
@@ -7074,7 +7056,7 @@ snapshots:
|
|||||||
|
|
||||||
json-schema-traverse@1.0.0: {}
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
json-schema-typed@7.0.3: {}
|
json-schema-typed@8.0.1: {}
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
@@ -7224,11 +7206,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lie: 3.1.1
|
lie: 3.1.1
|
||||||
|
|
||||||
locate-path@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
p-locate: 3.0.0
|
|
||||||
path-exists: 3.0.0
|
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 4.1.0
|
p-locate: 4.1.0
|
||||||
@@ -7338,7 +7315,7 @@ snapshots:
|
|||||||
|
|
||||||
mimic-fn@2.1.0: {}
|
mimic-fn@2.1.0: {}
|
||||||
|
|
||||||
mimic-fn@3.1.0: {}
|
mimic-function@5.0.1: {}
|
||||||
|
|
||||||
mimic-response@1.0.1: {}
|
mimic-response@1.0.1: {}
|
||||||
|
|
||||||
@@ -7592,10 +7569,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
p-locate@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
p-limit: 2.3.0
|
|
||||||
|
|
||||||
p-locate@4.1.0:
|
p-locate@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 2.3.0
|
p-limit: 2.3.0
|
||||||
@@ -7638,8 +7611,6 @@ snapshots:
|
|||||||
|
|
||||||
path-browserify@1.0.1: {}
|
path-browserify@1.0.1: {}
|
||||||
|
|
||||||
path-exists@3.0.0: {}
|
|
||||||
|
|
||||||
path-exists@4.0.0: {}
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-exists@5.0.0: {}
|
path-exists@5.0.0: {}
|
||||||
@@ -7756,10 +7727,6 @@ snapshots:
|
|||||||
exsolve: 1.0.7
|
exsolve: 1.0.7
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
pkg-up@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
find-up: 3.0.0
|
|
||||||
|
|
||||||
plist@3.1.0:
|
plist@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@xmldom/xmldom': 0.8.11
|
'@xmldom/xmldom': 0.8.11
|
||||||
@@ -8258,6 +8225,8 @@ snapshots:
|
|||||||
'@tokenizer/token': 0.3.0
|
'@tokenizer/token': 0.3.0
|
||||||
peek-readable: 4.1.0
|
peek-readable: 4.1.0
|
||||||
|
|
||||||
|
stubborn-fs@1.2.5: {}
|
||||||
|
|
||||||
sumchecker@3.0.1:
|
sumchecker@3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -8278,6 +8247,8 @@ snapshots:
|
|||||||
|
|
||||||
symbol-observable@1.2.0: {}
|
symbol-observable@1.2.0: {}
|
||||||
|
|
||||||
|
tagged-tag@1.0.0: {}
|
||||||
|
|
||||||
tar@6.2.1:
|
tar@6.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr: 2.0.0
|
chownr: 2.0.0
|
||||||
@@ -8367,7 +8338,9 @@ snapshots:
|
|||||||
type-fest@0.13.1:
|
type-fest@0.13.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
type-fest@2.19.0: {}
|
type-fest@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
tagged-tag: 1.0.0
|
||||||
|
|
||||||
type-is@2.0.1:
|
type-is@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8594,6 +8567,8 @@ snapshots:
|
|||||||
|
|
||||||
webpack-virtual-modules@0.6.2: {}
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
||||||
|
when-exit@2.1.4: {}
|
||||||
|
|
||||||
which-module@2.0.1: {}
|
which-module@2.0.1: {}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
|
|||||||
@@ -110,10 +110,8 @@ const min = () => window.electron.ipcRenderer.send("win-min");
|
|||||||
// 最大化或还原
|
// 最大化或还原
|
||||||
const maxOrRes = () => {
|
const maxOrRes = () => {
|
||||||
if (window.electron.ipcRenderer.sendSync("win-state")) {
|
if (window.electron.ipcRenderer.sendSync("win-state")) {
|
||||||
isMax.value = false;
|
|
||||||
window.electron.ipcRenderer.send("win-restore");
|
window.electron.ipcRenderer.send("win-restore");
|
||||||
} else {
|
} else {
|
||||||
isMax.value = true;
|
|
||||||
window.electron.ipcRenderer.send("win-max");
|
window.electron.ipcRenderer.send("win-max");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -198,9 +196,15 @@ const setSelect = (key: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取窗口状态
|
// 获取窗口状态并监听主进程的状态变更
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
isMax.value = window.electron.ipcRenderer.sendSync("win-state");
|
isMax.value = window.electron.ipcRenderer.sendSync("win-state");
|
||||||
|
window.electron.ipcRenderer.on(
|
||||||
|
"win-state-change",
|
||||||
|
(_event, value: boolean) => {
|
||||||
|
isMax.value = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -290,7 +290,9 @@ class Player {
|
|||||||
if (currentSessionId !== this.playSessionId) return;
|
if (currentSessionId !== this.playSessionId) return;
|
||||||
if (!isElectron) window.document.title = "SPlayer";
|
if (!isElectron) window.document.title = "SPlayer";
|
||||||
// ipc
|
// ipc
|
||||||
if (isElectron) window.electron.ipcRenderer.send("play-status-change", false);
|
if (isElectron) {
|
||||||
|
window.electron.ipcRenderer.send("play-status-change", false);
|
||||||
|
}
|
||||||
console.log("⏸️ song pause:", playSongData);
|
console.log("⏸️ song pause:", playSongData);
|
||||||
});
|
});
|
||||||
// 结束
|
// 结束
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"electron.vite.config.*",
|
"electron.vite.config.*",
|
||||||
"electron/**/*",
|
"electron/**/*",
|
||||||
"electron/main/index.ts",
|
"electron/main/index.ts",
|
||||||
"electron/main/logger.ts",
|
"electron/main/logger/index.ts",
|
||||||
"electron/main/store.ts",
|
"electron/main/store/index.ts",
|
||||||
"electron/main/utils.ts",
|
"electron/main/utils.ts",
|
||||||
"electron/main/index.d.ts",
|
"electron/main/index.d.ts",
|
||||||
"electron/preload/index.d.ts",
|
"electron/preload/index.d.ts",
|
||||||
|
|||||||
183
web/loading.html
183
web/loading.html
@@ -1,183 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>SPlayer</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color: #fff;
|
|
||||||
--font-coloe: #000;
|
|
||||||
--primary: #f55e55;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--bg-color: #161616;
|
|
||||||
--font-coloe: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
user-select: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--primary);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 2.5em;
|
|
||||||
height: 2.5em;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
animation: bblFadInOut 1.8s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
color: var(--primary);
|
|
||||||
font-size: 6px;
|
|
||||||
position: relative;
|
|
||||||
text-indent: -9999em;
|
|
||||||
transform: translateZ(0);
|
|
||||||
animation-delay: -0.16s;
|
|
||||||
margin: 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before,
|
|
||||||
.loader:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:before {
|
|
||||||
left: -3.5em;
|
|
||||||
animation-delay: -0.32s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
left: 3.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#startApp {
|
|
||||||
opacity: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
outline: none;
|
|
||||||
border: 2px solid var(--primary);
|
|
||||||
height: 36px;
|
|
||||||
width: 90px;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 12px;
|
|
||||||
color: var(--primary);
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bblFadInOut {
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 2.5em 0 -1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
box-shadow: 0 2.5em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="logo">
|
|
||||||
<svg
|
|
||||||
t="1663641871751"
|
|
||||||
class="icon"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="11550"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
width="200"
|
|
||||||
height="200"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M511.764091 131.708086a446.145957 446.145957 0 1 0 446.145957 446.145957 446.145957 446.145957 0 0 0-446.145957-446.145957z m0 519.76004A71.829499 71.829499 0 1 1 583.59359 580.530919 72.275645 72.275645 0 0 1 511.764091 651.468126z"
|
|
||||||
fill="#F55E55"
|
|
||||||
p-id="11551"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M802.205109 0.541175l-168.197026 37.030114a67.814185 67.814185 0 0 0-53.091369 66.029602V223.614153l3.569168 349.778431h114.213365V223.614153h108.859613a26.322611 26.322611 0 0 0 26.768758-26.322611V26.863786a26.768757 26.768757 0 0 0-32.122509-26.322611z"
|
|
||||||
fill="#F9BBB8"
|
|
||||||
p-id="11552"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M511.764091 386.457428a186.935156 186.935156 0 1 0 186.935156 186.48901A186.935156 186.935156 0 0 0 511.764091 386.457428z m0 264.564552a71.383353 71.383353 0 1 1 71.383353-71.383353 71.383353 71.383353 0 0 1-71.383353 71.383353z"
|
|
||||||
fill="#F9BBB8"
|
|
||||||
p-id="11553"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="loader"></div>
|
|
||||||
<button id="startApp">直接启动</button>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
<span>SPlayer · Copyright © <span id="year"></span> IMSYY</span>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const startAppDom = document.getElementById("startApp");
|
|
||||||
|
|
||||||
// 更改年份
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
document.getElementById("year").innerHTML = currentYear;
|
|
||||||
|
|
||||||
startAppDom.addEventListener("click", () => {
|
|
||||||
window.electron.ipcRenderer.send("win-loaded");
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
startAppDom.style.opacity = 1;
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
54
web/loading/index.html
Normal file
54
web/loading/index.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SPlayer</title>
|
||||||
|
<link rel="stylesheet" href="./style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="logo">
|
||||||
|
<svg t="1663641871751" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="11550" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
|
||||||
|
<path
|
||||||
|
d="M511.764091 131.708086a446.145957 446.145957 0 1 0 446.145957 446.145957 446.145957 446.145957 0 0 0-446.145957-446.145957z m0 519.76004A71.829499 71.829499 0 1 1 583.59359 580.530919 72.275645 72.275645 0 0 1 511.764091 651.468126z"
|
||||||
|
fill="#F55E55" p-id="11551"></path>
|
||||||
|
<path
|
||||||
|
d="M802.205109 0.541175l-168.197026 37.030114a67.814185 67.814185 0 0 0-53.091369 66.029602V223.614153l3.569168 349.778431h114.213365V223.614153h108.859613a26.322611 26.322611 0 0 0 26.768758-26.322611V26.863786a26.768757 26.768757 0 0 0-32.122509-26.322611z"
|
||||||
|
fill="#F9BBB8" p-id="11552"></path>
|
||||||
|
<path
|
||||||
|
d="M511.764091 386.457428a186.935156 186.935156 0 1 0 186.935156 186.48901A186.935156 186.935156 0 0 0 511.764091 386.457428z m0 264.564552a71.383353 71.383353 0 1 1 71.383353-71.383353 71.383353 71.383353 0 0 1-71.383353 71.383353z"
|
||||||
|
fill="#F9BBB8" p-id="11553"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="loader"></div>
|
||||||
|
<button id="startApp">直接启动</button>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<span>SPlayer · Copyright © <span id="year"></span> IMSYY</span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const startAppDom = document.getElementById("startApp");
|
||||||
|
|
||||||
|
// 更改年份
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
document.getElementById("year").innerHTML = currentYear;
|
||||||
|
|
||||||
|
startAppDom.addEventListener("click", () => {
|
||||||
|
window.electron.ipcRenderer.send("win-loaded");
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
startAppDom.style.opacity = 1;
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
115
web/loading/style.css
Normal file
115
web/loading/style.css
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
:root {
|
||||||
|
--bg-color: #fff;
|
||||||
|
--font-coloe: #000;
|
||||||
|
--primary: #f55e55;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg-color: #161616;
|
||||||
|
--font-coloe: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
user-select: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--primary);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader,
|
||||||
|
.loader:before,
|
||||||
|
.loader:after {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 2.5em;
|
||||||
|
height: 2.5em;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
animation: bblFadInOut 1.8s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 6px;
|
||||||
|
position: relative;
|
||||||
|
text-indent: -9999em;
|
||||||
|
transform: translateZ(0);
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
margin: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:before,
|
||||||
|
.loader:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:before {
|
||||||
|
left: -3.5em;
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:after {
|
||||||
|
left: 3.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#startApp {
|
||||||
|
opacity: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
outline: none;
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
height: 36px;
|
||||||
|
width: 90px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bblFadInOut {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 2.5em 0 -1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
box-shadow: 0 2.5em 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user