feat: 完善剩余功能

This commit is contained in:
ethan.chen
2025-11-05 14:24:15 +08:00
parent f67a3835fa
commit 73945c40e5
8 changed files with 105 additions and 59 deletions

View File

@@ -9,7 +9,6 @@ export const config = {
// 歌词配置 // 歌词配置
lyrics: { lyrics: {
cacheDir: "./lyrics",
apiUrl: "http://123.57.93.143:28883", apiUrl: "http://123.57.93.143:28883",
apiKey: "fzt_tom", apiKey: "fzt_tom",
}, },

View File

@@ -4,6 +4,7 @@
*/ */
import { MusicStatus } from "./apple_music.ts"; import { MusicStatus } from "./apple_music.ts";
import { DatabaseSync } from 'node:sqlite'
export interface LyricsData { export interface LyricsData {
current_lyric_time: number | null; current_lyric_time: number | null;
@@ -37,14 +38,23 @@ let lyricsCache: LyricsCache = {
}; };
const DB_PATH = "./lyrics.db"; const DB_PATH = "./lyrics.db";
const db = new DatabaseSync(DB_PATH);
/** /**
* 初始化数据库 * 初始化数据库
*/ */
export async function initDB(): Promise<void> { export async function initDB(): Promise<void> {
try { try {
// 确保歌词目录存在 db.exec(`
await Deno.mkdir("./lyrics", { recursive: true }); CREATE TABLE IF NOT EXISTS lyrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
track_name TEXT NOT NULL,
artist TEXT NOT NULL,
lyrics_text TEXT NOT NULL,
source TEXT NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT 0
)
`);
console.log(`数据库初始化完成: ${DB_PATH}`); console.log(`数据库初始化完成: ${DB_PATH}`);
} catch (error) { } catch (error) {
console.error(`数据库初始化失败: ${error}`); console.error(`数据库初始化失败: ${error}`);
@@ -72,11 +82,8 @@ async function loadLyricsFromDB(
artist: string artist: string
): Promise<string | null> { ): Promise<string | null> {
try { try {
const filePath = `./lyrics/${encodeURIComponent( const content = db.prepare(`SELECT lyrics_text FROM lyrics WHERE track_name = ? AND artist = ?`).get(trackName, artist);
trackName return content.lyrics_text;
)}_${encodeURIComponent(artist)}.lrc`;
const content = await Deno.readTextFile(filePath);
return content;
} catch (error) { } catch (error) {
return null; return null;
} }
@@ -85,25 +92,29 @@ async function loadLyricsFromDB(
/** /**
* 保存歌词到数据库 * 保存歌词到数据库
*/ */
async function saveLyrics( export async function saveLyrics(
trackName: string, trackName: string,
artist: string, artist: string,
lyricsText: string lyricsText: string,
source: string
): Promise<void> { ): Promise<void> {
try { try {
const filePath = `./lyrics/${encodeURIComponent( db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
trackName db.prepare(`INSERT INTO lyrics (track_name, artist, lyrics_text, source) VALUES (?, ?, ?, ?)`).run(trackName, artist, lyricsText, source);
)}_${encodeURIComponent(artist)}.lrc`; lyricsCache.track_name = trackName;
await Deno.writeTextFile(filePath, lyricsText); lyricsCache.artist = artist;
lyricsCache.lyrics_text = lyricsText;
lyricsCache.source = source;
console.log(`保存歌词成功: ${trackName} - ${artist}`);
} catch (error) { } catch (error) {
console.error(`保存歌词失败: ${error}`); console.error(`保存歌词失败: ${error}`);
} }
} }
/** /**
* 从网易云音乐 API 搜索歌词 * 从服务器 API 搜索歌词
*/ */
async function searchLyrics( export async function searchLyrics(
trackName: string, trackName: string,
artistName: string artistName: string
): Promise<string | null> { ): Promise<string | null> {
@@ -234,6 +245,18 @@ function formatLrcLyrics(
}; };
} }
/**
* 删除歌词
*/
export async function deleteLyrics(trackName: string, artist: string): Promise<void> {
try {
db.prepare(`DELETE FROM lyrics WHERE track_name = ? AND artist = ?`).run(trackName, artist);
console.log(`删除歌词成功: ${trackName} - ${artist}`);
} catch (error) {
console.error(`删除歌词失败: ${error}`);
}
}
/** /**
* 获取当前歌词数据 * 获取当前歌词数据
*/ */
@@ -276,9 +299,9 @@ export async function getLyricsData(status: MusicStatus): Promise<LyricsData> {
const lyricsText = await searchLyrics(trackName || "", artist || ""); const lyricsText = await searchLyrics(trackName || "", artist || "");
if (lyricsText) { if (lyricsText) {
// 保存到数据库 // 保存到数据库
await saveLyrics(trackName || "", artist || "", lyricsText); await saveLyrics(trackName || "", artist || "", lyricsText, "server");
lyricsCache.lyrics_text = lyricsText; lyricsCache.lyrics_text = lyricsText;
lyricsCache.source = "netease"; lyricsCache.source = "server";
} }
} }
} }

View File

@@ -5,10 +5,9 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { cors } from "cors"; import { cors } from "cors";
import { logger } from "logger";
import { ConnectionManager } from "./websocket_manager.ts"; import { ConnectionManager } from "./websocket_manager.ts";
import { getMusicStatus, controlMusic } from "./apple_music.ts"; import { getMusicStatus, controlMusic } from "./apple_music.ts";
import { getLyricsData } from "./lyrics.ts"; import { getLyricsData, searchLyrics, saveLyrics, deleteLyrics } from "./lyrics.ts";
export interface ControlRequest { export interface ControlRequest {
action: "playpause" | "previous" | "next" | "seek"; action: "playpause" | "previous" | "next" | "seek";
@@ -16,7 +15,7 @@ export interface ControlRequest {
} }
export interface SearchLyricsRequest { export interface SearchLyricsRequest {
title: string; track_name: string;
artist: string; artist: string;
} }
@@ -25,6 +24,17 @@ export interface GetLyricsFromIdRequest {
artist: string; artist: string;
} }
export interface SaveLyricsRequest {
track_name: string;
artist: string;
lyrics: string;
}
export interface DeleteLyricsRequest {
track_name: string;
artist: string;
}
/** /**
* 注册所有路由 * 注册所有路由
*/ */
@@ -89,27 +99,50 @@ export function registerRoutes(app: Hono, manager: ConnectionManager): void {
} }
}); });
app.delete("/lyrics", async (c) => {
try {
const body = (await c.req.json()) as DeleteLyricsRequest;
const { track_name, artist } = body;
await deleteLyrics(track_name, artist);
return c.json({ status: "success", message: "歌词删除成功" });
} catch (error) {
return c.json({ status: "error", error: String(error) }, 500);
}
});
// 搜索歌词 // 搜索歌词
app.post("/lyrics/search", async (c) => { app.post("/lyrics/search", async (c) => {
try { try {
const body = (await c.req.json()) as SearchLyricsRequest; const body = (await c.req.json()) as SearchLyricsRequest;
if (!body.title) { const { track_name, artist } = body;
return c.json({ status: "error", message: "缺少 title 参数" }, 400); const lyricsData = await searchLyrics(track_name, artist);
}
// 这里可以实现歌词搜索逻辑
// 暂时返回空结果
return c.json({ return c.json({
status: "success", status: "success",
lyrics: null, songs: [{
message: "歌词搜索功能待实现", name: track_name,
artist: artist,
lyrics: lyricsData,
}],
message: "歌词搜索成功",
}); });
} catch (error) { } catch (error) {
return c.json({ status: "error", error: String(error) }, 500); return c.json({ status: "error", error: String(error) }, 500);
} }
}); });
// 保存歌词
app.post("/lyrics/save", async (c) => {
try {
const body = (await c.req.json()) as SaveLyricsRequest;
const { track_name, artist, lyrics } = body;
await saveLyrics(track_name, artist, lyrics, "search");
return c.json({ status: "success", message: "歌词保存成功" });
} catch (error) {
return c.json({ status: "error", error: String(error) }, 500);
}
});
// 根据歌曲信息获取歌词 // 根据歌曲信息获取歌词
app.post("/lyrics/get", async (c) => { app.post("/lyrics/get", async (c) => {
try { try {

View File

@@ -102,3 +102,6 @@ ENV/
# Local dev configurations # Local dev configurations
config.local.js config.local.js
# SQLite database
lyrics.db

View File

@@ -199,12 +199,6 @@ app.on("activate", function () {
} }
}); });
// 应用退出前
app.on("before-quit", () => {
// 清理Python进程
backendService.stopPythonBackend();
});
// 监听第二实例启动事件 // 监听第二实例启动事件
app.on("second-instance", (event, commandLine, workingDirectory) => { app.on("second-instance", (event, commandLine, workingDirectory) => {
// 当尝试启动第二个实例时,让主窗口获得焦点 // 当尝试启动第二个实例时,让主窗口获得焦点

View File

@@ -267,7 +267,7 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
* @returns {Array} 菜单模板数组 * @returns {Array} 菜单模板数组
*/ */
function getMenuTemplate() { function getMenuTemplate() {
return [ const res = [
{ {
label: i18next.t("menu.lockWindow"), label: i18next.t("menu.lockWindow"),
click: () => { click: () => {
@@ -286,12 +286,6 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
mainWindow.reload(); mainWindow.reload();
}, },
}, },
{
label: i18next.t("menu.openDevTools"),
click: () => {
mainWindow.webContents.openDevTools();
},
},
{ {
label: i18next.t("menu.language"), label: i18next.t("menu.language"),
submenu: [ submenu: [
@@ -339,6 +333,16 @@ function createWindow(frontendUrl, frontendPort, backendPort, isDev) {
}, },
}, },
]; ];
if (isDev) {
res.push({
label: i18next.t("menu.openDevTools"),
click: () => {
mainWindow.webContents.openDevTools();
searchWindow && searchWindow.webContents.openDevTools();
},
});
}
return res;
} }
/** /**

View File

@@ -25,19 +25,13 @@ async function sendControlCommand(action, position = null) {
data: payload data: payload
}); });
if (!response.ok) {
throw new Error(`控制请求失败: ${response.status} ${response.statusText}`);
}
const result = await response.json();
// 检查结果状态如果Apple Music未运行提供明确的错误信息 // 检查结果状态如果Apple Music未运行提供明确的错误信息
if (result.status === 'error' && result.message === 'notrunning') { if (response.status === 'error' && response.message === 'notrunning') {
console.warn('Apple Music未运行无法执行控制命令'); console.warn('Apple Music未运行无法执行控制命令');
throw new Error('Apple Music未运行请先启动Apple Music应用'); throw new Error('Apple Music未运行请先启动Apple Music应用');
} }
return result; return response;
} catch (error) { } catch (error) {
console.error(`${action}控制请求错误:`, error); console.error(`${action}控制请求错误:`, error);
throw error; throw error;

View File

@@ -50,7 +50,7 @@
> >
<div class="result-info"> <div class="result-info">
<div class="track-name">{{ result.name }}</div> <div class="track-name">{{ result.name }}</div>
<div class="artist-name">{{ getArtistName(result.artists) }}</div> <div class="artist-name">{{ result.artist }}</div>
</div> </div>
<div class="result-actions"> <div class="result-actions">
<button <button
@@ -88,10 +88,6 @@ const hasSearched = ref(false);
const selectedIndex = ref(-1); const selectedIndex = ref(-1);
const searchResults = ref([]); const searchResults = ref([]);
const getArtistName = (artists) => {
return artists.map(artist => artist.name).join(', ');
};
import request from '../utils/request'; import request from '../utils/request';
import { useLyricsStore } from '../store'; import { useLyricsStore } from '../store';
const lyricsStore = useLyricsStore(); const lyricsStore = useLyricsStore();
@@ -139,12 +135,12 @@ const returnToSearch = () => {
const selectResult = (index) => { const selectResult = (index) => {
request({ request({
url: '/lyrics/getLyricsFromId', url: '/lyrics/save',
method: 'POST', method: 'POST',
data: { data: {
id: searchResults.value[index].id,
track_name: lyricsStore.trackName, track_name: lyricsStore.trackName,
artist: lyricsStore.artist artist: lyricsStore.artist,
lyrics: searchResults.value[index].lyrics
} }
}); });
}; };
@@ -312,7 +308,7 @@ h2 {
height: 40px; height: 40px;
border-radius: 6px; border-radius: 6px;
white-space: nowrap; white-space: nowrap;
} }
.results-list { .results-list {
display: flex; display: flex;