Merge branch 'main' into feat/menu-expand

This commit is contained in:
Alger
2025-09-14 00:00:46 +08:00
committed by GitHub
33 changed files with 2314 additions and 1230 deletions
+3
View File
@@ -16,6 +16,7 @@ import { setupUpdateHandlers } from './modules/update';
import { createMainWindow, initializeWindowManager, setAppQuitting } from './modules/window';
import { initWindowSizeManager } from './modules/window-size';
import { startMusicApi } from './server';
import { initializeOtherApi } from './modules/otherApi';
// 导入所有图标
const iconPath = join(__dirname, '../../resources');
@@ -38,6 +39,8 @@ function initialize() {
// 初始化文件管理
initializeFileManager();
// 初始化其他 API (搜索建议等)
initializeOtherApi();
// 初始化窗口管理
initializeWindowManager();
// 初始化字体管理
+101 -11
View File
@@ -9,7 +9,8 @@ import * as mm from 'music-metadata';
import * as NodeID3 from 'node-id3';
import * as os from 'os';
import * as path from 'path';
import { FlacTagMap, writeFlacTags } from 'flac-tagger';
import sharp from 'sharp';
import { getStore } from './config';
const MAX_CONCURRENT_DOWNLOADS = 3;
@@ -275,6 +276,39 @@ export function initializeFileManager() {
}
}
});
// 处理导入自定义API插件的请求
ipcMain.handle('import-custom-api-plugin', async () => {
const result = await dialog.showOpenDialog({
title: '选择自定义音源配置文件',
filters: [{ name: 'JSON Files', extensions: ['json'] }],
properties: ['openFile']
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
const filePath = result.filePaths[0];
try {
const fileContent = fs.readFileSync(filePath, 'utf-8');
// 基础验证,确保它是个合法的JSON并且包含关键字段
const pluginData = JSON.parse(fileContent);
if (!pluginData.name || !pluginData.apiUrl) {
throw new Error('无效的插件文件,缺少 name 或 apiUrl 字段。');
}
return {
name: pluginData.name,
content: fileContent // 返回完整的JSON字符串
};
} catch (error: any) {
console.error('读取或解析插件文件失败:', error);
// 向渲染进程抛出错误,以便UI可以显示提示
throw new Error(`文件读取或解析失败: ${error.message}`);
}
});
}
/**
@@ -386,7 +420,7 @@ async function downloadMusic(
let formattedFilename = filename;
if (songInfo) {
// 准备替换变量
const artistName = songInfo.ar?.map((a: any) => a.name).join('/') || '未知艺术家';
const artistName = songInfo.ar?.map((a: any) => a.name).join('') || '未知艺术家';
const songName = songInfo.name || filename;
const albumName = songInfo.al?.name || '未知专辑';
@@ -576,8 +610,39 @@ async function downloadMusic(
timeout: 10000
});
// 获取封面图片的buffer
coverImageBuffer = Buffer.from(coverResponse.data);
const originalCoverBuffer = Buffer.from(coverResponse.data);
const TWO_MB = 2 * 1024 * 1024;
// 检查图片大小是否超过2MB
if (originalCoverBuffer.length > TWO_MB) {
const originalSizeMB = (originalCoverBuffer.length / (1024 * 1024)).toFixed(2);
console.log(`封面图大于2MB (${originalSizeMB} MB),开始压缩...`);
try {
// 使用 sharp 进行压缩
coverImageBuffer = await sharp(originalCoverBuffer)
.resize({
width: 1600,
height: 1600,
fit: 'inside',
withoutEnlargement: true
})
.jpeg({
quality: 80,
mozjpeg: true
})
.toBuffer();
const compressedSizeMB = (coverImageBuffer.length / (1024 * 1024)).toFixed(2);
console.log(`封面图压缩完成,新大小: ${compressedSizeMB} MB`);
} catch (compressionError) {
console.error('封面图压缩失败,将使用原图:', compressionError);
coverImageBuffer = originalCoverBuffer; // 如果压缩失败,则回退使用原始图片
}
} else {
// 如果图片不大于2MB,直接使用原图
coverImageBuffer = originalCoverBuffer;
}
console.log('封面已准备好,将写入元数据');
}
}
@@ -588,7 +653,7 @@ async function downloadMusic(
const fileFormat = fileExtension.toLowerCase();
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/ ') || '未知艺术家';
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('') || '未知艺术家';
// 根据文件类型处理元数据
if (['.mp3'].includes(fileFormat)) {
@@ -598,7 +663,7 @@ async function downloadMusic(
NodeID3.removeTags(finalFilePath);
const tags = {
title: filename,
title: songInfo?.name,
artist: artistNames,
TPE1: artistNames,
TPE2: artistNames,
@@ -634,10 +699,35 @@ async function downloadMusic(
} catch (err) {
console.error('Error writing ID3 tags:', err);
}
} else {
// 对于非MP3文件,使用music-metadata来写入元数据可能需要专门的库
// 或者根据不同文件类型使用专用工具,暂时只记录但不处理
console.log(`文件类型 ${fileFormat} 不支持使用NodeID3写入标签,跳过元数据写入`);
} else if (['.flac'].includes(fileFormat)) {
try {
const tagMap: FlacTagMap = {
TITLE: songInfo?.name,
ARTIST: artistNames,
ALBUM: songInfo?.al?.name || songInfo?.song?.album?.name || songInfo?.name || filename,
LYRICS: lyricsContent || '',
TRACKNUMBER: songInfo?.no ? String(songInfo.no) : undefined,
DATE: songInfo?.publishTime
? new Date(songInfo.publishTime).getFullYear().toString()
: undefined
};
await writeFlacTags(
{
tagMap,
picture: coverImageBuffer
? {
buffer: coverImageBuffer,
mime: 'image/jpeg'
}
: undefined
},
finalFilePath
);
console.log('FLAC tags written successfully');
} catch (err) {
console.error('Error writing FLAC tags:', err);
}
}
// 保存下载信息
@@ -683,7 +773,7 @@ async function downloadMusic(
// 发送桌面通知
try {
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/') ||
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('') ||
'未知艺术家';
const notification = new Notification({
title: '下载完成',
+28
View File
@@ -0,0 +1,28 @@
import axios from 'axios';
import { ipcMain } from 'electron';
/**
* 初始化其他杂项 API(如搜索建议等)
*/
export function initializeOtherApi() {
// 搜索建议(从酷狗获取)
ipcMain.handle('get-search-suggestions', async (_, keyword: string) => {
if (!keyword || !keyword.trim()) {
return [];
}
try {
console.log(`[Main Process Proxy] Forwarding suggestion request for: ${keyword}`);
const response = await axios.get('http://msearchcdn.kugou.com/new/app/i/search.php', {
params: {
cmd: 302,
keyword: keyword
},
timeout: 5000
});
return response.data;
} catch (error: any) {
console.error('[Main Process Proxy] Failed to fetch search suggestions:', error.message);
return [];
}
});
}
+3 -1
View File
@@ -28,5 +28,7 @@
"contentZoomFactor": 1,
"autoTheme": false,
"manualTheme": "light",
"isMenuExpanded": false
"isMenuExpanded": false,
"customApiPlugin": "",
"customApiPluginName": ""
}