diff --git a/core/qzplayer.exe b/core/qzplayer.exe index 75dd78e..dc1a675 100644 Binary files a/core/qzplayer.exe and b/core/qzplayer.exe differ diff --git a/dist-electron/main.js b/dist-electron/main.js deleted file mode 100644 index bbdfa91..0000000 --- a/dist-electron/main.js +++ /dev/null @@ -1,1162 +0,0 @@ -var __defProp = Object.defineProperty; -var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); -import { app, ipcMain, BrowserWindow, Menu } from "electron"; -import { createRequire } from "node:module"; -import { fileURLToPath } from "node:url"; -import path$1 from "node:path"; -import fs$1 from "node:fs"; -import { spawn } from "child_process"; -import { Socket } from "net"; -import { EventEmitter } from "events"; -import path from "path"; -import http from "http"; -import { Readable } from "stream"; -import fs from "fs"; -class QzpController extends EventEmitter { - constructor(ipcPath) { - super(); - __publicField(this, "process", null); - __publicField(this, "socket", null); - __publicField(this, "ipcPath"); - __publicField(this, "messageBuffer", ""); - this.ipcPath = ipcPath || this.getIpcPath(); - } - getIpcPath() { - if (process.platform === "win32") { - return "\\\\.\\pipe\\qzplayer"; - } - return "/tmp/qzmusic_mpv_socket"; - } - getCorePath() { - const appRoot = process.env.APP_ROOT || process.cwd(); - if (process.platform === "win32") { - return path.join(appRoot, "core", "qzplayer.exe"); - } - return "qzplayer"; - } - start() { - const playerPath = this.getCorePath(); - console.log("Starting QZPlayer from:", playerPath); - this.process = spawn(playerPath); - this.process.on("error", (err) => { - console.error("Failed to start QZPlayer:", err); - this.emit("error", err); - }); - this.process.on("exit", (code, signal) => { - var _a; - console.log(`QZPlayer exited with code ${code} and signal ${signal}`); - this.emit("exit", { code, signal }); - (_a = this.socket) == null ? void 0 : _a.destroy(); - }); - this.tryConnect(); - } - tryConnect(retries = 10) { - if (retries <= 0) { - console.error("Could not connect to QZPlayer socket after multiple attempts."); - return; - } - setTimeout(() => { - this.socket = new Socket(); - this.socket.on("connect", () => { - console.log("Connected to QZPlayer IPC socket"); - this.emit("ready"); - this.send(["observe_property", 1, "pause"]); - this.send(["observe_property", 2, "time-pos"]); - this.send(["observe_property", 3, "duration"]); - this.send(["observe_property", 4, "idle-active"]); - this.send(["observe_property", 5, "eof-reached"]); - this.send(["set_property", "volume", 50]); - }); - this.socket.on("data", (data) => { - this.handleData(data); - }); - this.socket.on("error", (_) => { - var _a; - (_a = this.socket) == null ? void 0 : _a.destroy(); - this.tryConnect(retries - 1); - }); - this.socket.connect(this.ipcPath); - }, 500); - } - handleData(data) { - const raw = data.toString(); - this.messageBuffer += raw; - const messages = this.messageBuffer.split("\n"); - this.messageBuffer = messages.pop() || ""; - for (const msg of messages) { - if (!msg.trim()) continue; - console.log("[IPC]", msg); - try { - const json = JSON.parse(msg); - this.emit("message", json); - if (json.event) { - this.emit("event", json); - } - } catch (e) { - console.error("Failed to parse QZPlayer message:", msg); - } - } - } - async send(command) { - if (!this.socket || this.socket.destroyed) { - console.warn("QZPlayer socket not connected"); - return; - } - const payload = JSON.stringify({ command }); - console.log("[QZPlayer TX]", payload); - this.socket.write(payload + "\n"); - } - // Convenience methods - async load(url) { - return this.send(["loadfile", url]); - } - async play() { - return this.send(["set_property", "pause", false]); - } - async pause() { - return this.send(["set_property", "pause", true]); - } - async togglePause() { - return this.send(["cycle", "pause"]); - } - async stop() { - return this.send(["stop"]); - } - async setVolume(vol) { - return this.send(["set_property", "volume", vol]); - } - async seek(seconds) { - return this.send(["seek", seconds, "absolute"]); - } - destroy() { - if (this.process) { - console.log("Killing QZPlayer process..."); - this.process.kill(); - this.process = null; - } - if (this.socket) { - this.socket.destroy(); - this.socket = null; - } - } -} -const require$2 = createRequire(import.meta.url); -class PluginSystem { - constructor(pluginId) { - __publicField(this, "pluginId"); - __publicField(this, "plugin", null); - this.pluginId = pluginId; - this.loadPlugin(); - } - async search(query, page, limit) { - var _a, _b; - if (!((_b = (_a = this.plugin) == null ? void 0 : _a.musicSearch) == null ? void 0 : _b.search)) { - return { - list: [], - total: 0, - allPage: 0, - error: "Search not implemented" - }; - } - try { - return await this.plugin.musicSearch.search(query, page, limit); - } catch (e) { - return { - list: [], - total: 0, - allPage: 0, - error: e.message || "Plugin search error" - }; - } - } - loadPlugin() { - try { - const pluginPath = path.join( - app.getPath("userData"), - "plugins", - this.pluginId, - "index.js" - ); - if (!fs.existsSync(pluginPath)) { - throw new Error(`Plugin ${this.pluginId} not found`); - } - delete require$2.cache[require$2.resolve(pluginPath)]; - this.plugin = require$2(pluginPath); - } catch (e) { - console.error(`[PluginSystem] load failed:`, e); - this.plugin = null; - } - } - async getUrl(id, quality) { - var _a; - if (!((_a = this.plugin) == null ? void 0 : _a.getUrl)) { - return { - success: false, - error: "getUrl not implemented" - }; - } - try { - const url = await this.plugin.getUrl(id, quality); - if (typeof url !== "string" || !url.startsWith("http")) { - return { - success: false, - error: "Invalid URL scheme" - }; - } - return { - success: true, - url - }; - } catch (e) { - return { - success: false, - error: e.message || "plugin error" - }; - } - } -} -const PORT = 5266; -let CACHE_DIR = ""; -function ensureCacheDir() { - if (!CACHE_DIR) { - CACHE_DIR = path.join(app.getPath("userData"), "music", "cache"); - } - if (!fs.existsSync(CACHE_DIR)) { - try { - fs.mkdirSync(CACHE_DIR, { recursive: true }); - console.log(`[Proxy] Created cache directory: ${CACHE_DIR}`); - } catch (e) { - console.error("[Proxy] Failed to create cache directory:", e); - } - } - return CACHE_DIR; -} -const urlCache = /* @__PURE__ */ new Map(); -const downloadTasks = /* @__PURE__ */ new Map(); -function getCachePath(source, id, quality) { - const dir = ensureCacheDir(); - const safeId = id.replace(/[^a-z0-9]/gi, "_"); - return path.join(dir, `${source}-${safeId}-${quality}`); -} -function getMetadataPath(cachePath) { - return cachePath + ".meta"; -} -function getTempPath(cachePath) { - return cachePath + ".tmp"; -} -function readMetadata(cachePath) { - const metaPath = getMetadataPath(cachePath); - try { - if (fs.existsSync(metaPath)) { - return JSON.parse(fs.readFileSync(metaPath, "utf-8")); - } - } catch (e) { - console.error("[Proxy] Failed to read metadata:", e); - } - return null; -} -function writeMetadata(cachePath, metadata) { - const metaPath = getMetadataPath(cachePath); - try { - fs.writeFileSync(metaPath, JSON.stringify(metadata)); - } catch (e) { - console.error("[Proxy] Failed to write metadata:", e); - } -} -async function fetchFreshUrl(source, id, quality) { - try { - const plugin = new PluginSystem(source); - const result = await plugin.getUrl(id, quality); - if (result.success && result.url) { - urlCache.set(`${source}:${id}:${quality}`, { - url: result.url, - expiresAt: Date.now() + 3600 * 1e3 - }); - return result.url; - } - console.error(`[Proxy] Failed to fetch URL for ${source}:${id}`, result.error); - return null; - } catch (e) { - console.error(`[Proxy] Error fetching URL:`, e); - return null; - } -} -function isValidCacheFile(filePath) { - try { - const metadata = readMetadata(filePath); - if (!metadata || !metadata.complete) return false; - if (!fs.existsSync(filePath)) return false; - const stat = fs.statSync(filePath); - return stat.size === metadata.totalSize && stat.size > 1024; - } catch { - return false; - } -} -function parseRangeHeader(range, fileSize) { - if (!range) return null; - const match = range.match(/bytes=(\d*)-(\d*)/); - if (!match) return null; - let start = match[1] ? parseInt(match[1], 10) : 0; - let end = match[2] ? parseInt(match[2], 10) : fileSize - 1; - if (start >= fileSize) return null; - end = Math.min(end, fileSize - 1); - return { start, end }; -} -function serveFromCache(req, res, filePath) { - try { - const metadata = readMetadata(filePath); - if (!metadata) { - console.warn("[Proxy] No metadata for cache file"); - return false; - } - const stat = fs.statSync(filePath); - const fileSize = stat.size; - const range = parseRangeHeader(req.headers.range, fileSize); - if (range) { - const { start, end } = range; - const chunksize = end - start + 1; - res.writeHead(206, { - "Content-Range": `bytes ${start}-${end}/${fileSize}`, - "Accept-Ranges": "bytes", - "Content-Length": chunksize, - "Content-Type": metadata.contentType || "audio/mpeg" - }); - const file = fs.createReadStream(filePath, { start, end }); - file.pipe(res); - file.on("error", (err) => { - console.error("[Proxy] Error reading cache file:", err); - if (!res.headersSent) { - res.writeHead(500); - res.end("Cache read error"); - } - }); - } else { - res.writeHead(200, { - "Content-Length": fileSize, - "Content-Type": metadata.contentType || "audio/mpeg", - "Accept-Ranges": "bytes" - }); - const stream = fs.createReadStream(filePath); - stream.pipe(res); - stream.on("error", (err) => { - console.error("[Proxy] Error reading cache file:", err); - }); - } - return true; - } catch (e) { - console.error("[Proxy] Error serving from cache:", e); - return false; - } -} -async function proxyRangeDirect(req, res, targetUrl, totalSize, contentType) { - const headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" - }; - if (req.headers.range) { - headers["Range"] = req.headers.range; - } - const response = await fetch(targetUrl, { - method: "GET", - headers - }); - if (!response.ok && response.status !== 206) { - throw { statusCode: response.status, statusText: response.statusText }; - } - const responseContentType = response.headers.get("content-type") || contentType || "audio/mpeg"; - const contentLength = response.headers.get("content-length"); - const contentRange = response.headers.get("content-range"); - if (response.status === 206 && contentRange) { - res.writeHead(206, { - "Content-Type": responseContentType, - "Content-Length": contentLength || "", - "Content-Range": contentRange, - "Accept-Ranges": "bytes" - }); - } else { - res.writeHead(200, { - "Content-Type": responseContentType, - "Content-Length": contentLength || "", - "Accept-Ranges": "bytes" - }); - } - if (!response.body) { - res.end(); - return; - } - const nodeStream = Readable.fromWeb(response.body); - nodeStream.pipe(res); - return new Promise((resolve, reject) => { - nodeStream.on("end", resolve); - nodeStream.on("error", reject); - res.on("close", () => { - nodeStream.destroy(); - resolve(); - }); - }); -} -function serveFromPartialCache(req, res, filePath, task) { - try { - const tempPath = getTempPath(filePath); - if (!fs.existsSync(tempPath)) return false; - const range = parseRangeHeader(req.headers.range, task.totalSize); - if (!range) return false; - const { start, end } = range; - let actualSize; - try { - actualSize = fs.statSync(tempPath).size; - } catch { - return false; - } - if (end >= actualSize) { - return false; - } - const chunksize = end - start + 1; - res.writeHead(206, { - "Content-Range": `bytes ${start}-${end}/${task.totalSize}`, - "Accept-Ranges": "bytes", - "Content-Length": chunksize, - "Content-Type": task.contentType || "audio/mpeg" - }); - const file = fs.createReadStream(tempPath, { start, end }); - file.pipe(res); - file.on("error", (err) => { - console.error("[Proxy] Error reading partial cache:", err); - }); - return true; - } catch (e) { - console.error("[Proxy] Error serving from partial cache:", e); - return false; - } -} -function startBackgroundDownload(targetUrl, cacheFilePath, cacheKey) { - const existingTask = downloadTasks.get(cacheFilePath); - if (existingTask && existingTask.promise) { - console.log(`[Proxy] Reusing existing download task for ${cacheFilePath}`); - return existingTask; - } - const abortController = new AbortController(); - const task = { - currentSize: 0, - totalSize: 0, - contentType: "audio/mpeg", - abortController, - promise: null, - waiters: [] - }; - downloadTasks.set(cacheFilePath, task); - task.promise = (async () => { - const tempPath = getTempPath(cacheFilePath); - try { - try { - if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); - } catch { - } - const response = await fetch(targetUrl, { - method: "GET", - headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" - }, - signal: abortController.signal - }); - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); - } - const contentLength = parseInt(response.headers.get("content-length") || "0", 10); - const contentType = response.headers.get("content-type") || "audio/mpeg"; - if (!contentLength) { - console.warn("[Proxy] Background download: No content-length, skipping cache"); - downloadTasks.delete(cacheFilePath); - return; - } - task.totalSize = contentLength; - task.contentType = contentType; - console.log(`[Proxy] Background download started: ${cacheFilePath} (${contentLength} bytes)`); - const fileStream = fs.createWriteStream(tempPath); - if (!response.body) { - downloadTasks.delete(cacheFilePath); - return; - } - const nodeStream = Readable.fromWeb(response.body); - await new Promise((resolve, reject) => { - nodeStream.on("data", (chunk) => { - task.currentSize += chunk.length; - notifyWaiters(task); - if (task.currentSize % Math.floor(contentLength / 10) < chunk.length) { - const percent = Math.floor(task.currentSize / contentLength * 100); - console.log(`[Proxy] Download progress: ${percent}% (${task.currentSize}/${contentLength})`); - } - }); - nodeStream.pipe(fileStream); - fileStream.on("finish", () => { - try { - const stat = fs.statSync(tempPath); - if (stat.size === contentLength) { - fs.renameSync(tempPath, cacheFilePath); - writeMetadata(cacheFilePath, { - totalSize: contentLength, - contentType, - complete: true, - createdAt: Date.now() - }); - console.log(`[Proxy] Background download complete: ${cacheFilePath}`); - } else { - console.warn(`[Proxy] Incomplete download: ${stat.size}/${contentLength}`); - try { - fs.unlinkSync(tempPath); - } catch { - } - } - } catch (e) { - console.error("[Proxy] Failed to finalize cache:", e); - try { - fs.unlinkSync(tempPath); - } catch { - } - } - resolve(); - }); - fileStream.on("error", (err) => { - console.error("[Proxy] Background download write error:", err); - try { - fs.unlinkSync(tempPath); - } catch { - } - reject(err); - }); - nodeStream.on("error", (err) => { - console.error("[Proxy] Background download stream error:", err); - fileStream.destroy(); - try { - fs.unlinkSync(tempPath); - } catch { - } - reject(err); - }); - }); - } catch (e) { - if (e.name !== "AbortError") { - console.error("[Proxy] Background download failed:", e); - } - try { - fs.unlinkSync(tempPath); - } catch { - } - for (const waiter of task.waiters) { - waiter.reject(new Error("Download failed")); - } - task.waiters = []; - } finally { - downloadTasks.delete(cacheFilePath); - } - })(); - return task; -} -function notifyWaiters(task) { - const resolvedWaiters = []; - for (let i = 0; i < task.waiters.length; i++) { - const waiter = task.waiters[i]; - if (task.currentSize > waiter.end) { - waiter.resolve(); - resolvedWaiters.push(i); - } - } - for (let i = resolvedWaiters.length - 1; i >= 0; i--) { - task.waiters.splice(resolvedWaiters[i], 1); - } -} -async function proxyAndCache(req, res, targetUrl, cacheFilePath, cacheKey) { - const requestedRange = req.headers.range; - const isSeekRequest = requestedRange && !requestedRange.startsWith("bytes=0-"); - let task = downloadTasks.get(cacheFilePath); - if (task) { - console.log(`[Proxy] Download in progress: ${task.currentSize}/${task.totalSize}`); - if (requestedRange && task.totalSize > 0) { - const range = parseRangeHeader(requestedRange, task.totalSize); - if (range) { - const tempPath = getTempPath(cacheFilePath); - let actualSize = 0; - try { - if (fs.existsSync(tempPath)) { - actualSize = fs.statSync(tempPath).size; - } - } catch { - } - if (range.end < actualSize) { - console.log(`[Proxy] Serving range ${range.start}-${range.end} from partial cache (downloaded: ${actualSize})`); - if (serveFromPartialCache(req, res, cacheFilePath, task)) { - return; - } - } - console.log(`[Proxy] Range ${range.start}-${range.end} not cached yet (downloaded: ${actualSize}), proxying directly`); - await proxyRangeDirect(req, res, targetUrl, task.totalSize, task.contentType); - return; - } - } - await proxyRangeDirect(req, res, targetUrl, task.totalSize, task.contentType); - return; - } - if (isSeekRequest) { - console.log(`[Proxy] Seek request: ${requestedRange}, starting background download`); - startBackgroundDownload(targetUrl, cacheFilePath); - const headResponse = await fetch(targetUrl, { - method: "HEAD", - headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" - } - }); - const totalSize = parseInt(headResponse.headers.get("content-length") || "0", 10); - const contentType = headResponse.headers.get("content-type") || "audio/mpeg"; - await proxyRangeDirect(req, res, targetUrl, totalSize, contentType); - return; - } - await streamAndCache(req, res, targetUrl, cacheFilePath); -} -async function streamAndCache(req, res, targetUrl, cacheFilePath, cacheKey) { - const headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" - }; - const response = await fetch(targetUrl, { - method: "GET", - headers - }); - if (!response.ok) { - throw { statusCode: response.status, statusText: response.statusText }; - } - const contentLength = parseInt(response.headers.get("content-length") || "0", 10); - const contentType = response.headers.get("content-type") || "audio/mpeg"; - if (!contentLength) { - console.warn("[Proxy] No content-length, proxying without cache"); - res.writeHead(200, { - "Content-Type": contentType, - "Accept-Ranges": "bytes" - }); - if (response.body) { - const nodeStream2 = Readable.fromWeb(response.body); - nodeStream2.pipe(res); - } else { - res.end(); - } - return; - } - const tempPath = getTempPath(cacheFilePath); - try { - if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); - if (fs.existsSync(cacheFilePath)) fs.unlinkSync(cacheFilePath); - } catch { - } - const fileStream = fs.createWriteStream(tempPath); - const task = { - currentSize: 0, - totalSize: contentLength, - contentType, - abortController: null, - promise: null, - waiters: [] - }; - downloadTasks.set(cacheFilePath, task); - console.log(`[Proxy] Starting stream download: ${cacheFilePath} (${contentLength} bytes)`); - res.writeHead(200, { - "Content-Length": contentLength, - "Content-Type": contentType, - "Accept-Ranges": "bytes" - }); - if (!response.body) { - res.end(); - downloadTasks.delete(cacheFilePath); - return; - } - const nodeStream = Readable.fromWeb(response.body); - let clientConnected = true; - res.on("close", () => { - clientConnected = false; - console.log("[Proxy] Client disconnected"); - }); - return new Promise((resolve, reject) => { - nodeStream.on("data", (chunk) => { - task.currentSize += chunk.length; - fileStream.write(chunk); - if (clientConnected && !res.writableEnded) { - res.write(chunk); - } - notifyWaiters(task); - }); - nodeStream.on("end", () => { - fileStream.end(); - if (clientConnected && !res.writableEnded) { - res.end(); - } - }); - nodeStream.on("error", (err) => { - console.error("[Proxy] Stream error:", err); - fileStream.destroy(); - if (clientConnected && !res.headersSent) { - res.writeHead(502); - res.end("Proxy stream error"); - } - downloadTasks.delete(cacheFilePath); - try { - fs.unlinkSync(tempPath); - } catch { - } - reject(err); - }); - fileStream.on("finish", () => { - downloadTasks.delete(cacheFilePath); - try { - const stat = fs.statSync(tempPath); - if (stat.size === contentLength) { - fs.renameSync(tempPath, cacheFilePath); - writeMetadata(cacheFilePath, { - totalSize: contentLength, - contentType, - complete: true, - createdAt: Date.now() - }); - console.log(`[Proxy] Cache complete: ${cacheFilePath} (${contentLength} bytes)`); - } else { - console.warn(`[Proxy] Incomplete download: ${stat.size}/${contentLength}`); - try { - fs.unlinkSync(tempPath); - } catch { - } - } - } catch (e) { - console.error("[Proxy] Failed to finalize cache:", e); - try { - fs.unlinkSync(tempPath); - } catch { - } - } - resolve(); - }); - fileStream.on("error", (err) => { - console.error("[Proxy] File write error:", err); - downloadTasks.delete(cacheFilePath); - try { - fs.unlinkSync(tempPath); - } catch { - } - reject(err); - }); - }); -} -let persistCacheEnabled = true; -function getCacheDir() { - return ensureCacheDir(); -} -function getCacheSize() { - const dir = ensureCacheDir(); - if (!fs.existsSync(dir)) return "0 B"; - let totalSize = 0; - try { - const files = fs.readdirSync(dir); - for (const file of files) { - const filePath = path.join(dir, file); - try { - const stat = fs.statSync(filePath); - if (stat.isFile()) { - totalSize += stat.size; - } - } catch { - } - } - } catch (e) { - console.error("[Proxy] Error calculating cache size:", e); - } - if (totalSize < 1024) return `${totalSize} B`; - if (totalSize < 1024 * 1024) return `${(totalSize / 1024).toFixed(1)} KB`; - if (totalSize < 1024 * 1024 * 1024) return `${(totalSize / (1024 * 1024)).toFixed(1)} MB`; - return `${(totalSize / (1024 * 1024 * 1024)).toFixed(2)} GB`; -} -function setPersistCache(persist) { - persistCacheEnabled = persist; - console.log(`[Proxy] Cache persistence set to: ${persist}`); -} -function clearCacheNow() { - const dir = ensureCacheDir(); - if (fs.existsSync(dir)) { - console.log(`[Proxy] Clearing cache directory: ${dir}`); - for (const [, task] of downloadTasks) { - if (task.abortController) { - task.abortController.abort(); - } - } - downloadTasks.clear(); - try { - fs.rmSync(dir, { recursive: true, force: true }); - fs.mkdirSync(dir, { recursive: true }); - console.log("[Proxy] Cache cleared"); - } catch (e) { - console.error("[Proxy] Failed to clear cache:", e); - } - } -} -function cleanupCache() { - if (!persistCacheEnabled && CACHE_DIR && fs.existsSync(CACHE_DIR)) { - console.log(`[Proxy] Cleaning up cache directory: ${CACHE_DIR}`); - for (const [, task] of downloadTasks) { - if (task.abortController) { - task.abortController.abort(); - } - } - downloadTasks.clear(); - try { - fs.rmSync(CACHE_DIR, { recursive: true, force: true }); - console.log("[Proxy] Cache cleanup complete"); - } catch (e) { - console.error("[Proxy] Failed to cleanup cache:", e); - } - } -} -function cleanupTempFiles() { - const dir = ensureCacheDir(); - try { - const files = fs.readdirSync(dir); - const now = Date.now(); - for (const file of files) { - if (file.endsWith(".tmp")) { - const filePath = path.join(dir, file); - const cacheFilePath = filePath.replace(".tmp", ""); - if (!downloadTasks.has(cacheFilePath)) { - try { - const stat = fs.statSync(filePath); - if (now - stat.mtimeMs > 3600 * 1e3) { - fs.unlinkSync(filePath); - console.log(`[Proxy] Cleaned up stale temp file: ${file}`); - } - } catch { - } - } - } - } - } catch (e) { - console.error("[Proxy] Error cleaning up temp files:", e); - } -} -function startProxyServer(persistCache = true) { - persistCacheEnabled = persistCache; - console.log(`[Proxy] Persist cache: ${persistCache}`); - setInterval(cleanupTempFiles, 30 * 60 * 1e3); - const server = http.createServer(async (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Range"); - if (req.method === "OPTIONS") { - res.statusCode = 204; - res.end(); - return; - } - if (req.method === "HEAD") { - res.setHeader("Accept-Ranges", "bytes"); - res.statusCode = 200; - res.end(); - return; - } - try { - const parsedUrl = new URL(req.url || "", `http://localhost:${PORT}`); - if (parsedUrl.pathname !== "/music") { - res.writeHead(404); - res.end("Not Found"); - return; - } - const id = parsedUrl.searchParams.get("id"); - const source = parsedUrl.searchParams.get("source"); - const quality = parsedUrl.searchParams.get("quality") || "standard"; - if (!id || !source) { - res.writeHead(400); - res.end("Missing id or source"); - return; - } - const cacheKey = `${source}:${id}:${quality}`; - const cacheFilePath = getCachePath(source, id, quality); - if (isValidCacheFile(cacheFilePath)) { - console.log(`[Proxy] Serving from complete cache: ${cacheFilePath}`); - const success = serveFromCache(req, res, cacheFilePath); - if (success) return; - console.warn("[Proxy] Cache file invalid, cleaning up..."); - try { - fs.unlinkSync(cacheFilePath); - fs.unlinkSync(getMetadataPath(cacheFilePath)); - } catch { - } - } - let playUrl = null; - const memCached = urlCache.get(cacheKey); - if (memCached && Date.now() < memCached.expiresAt) { - playUrl = memCached.url; - } - if (!playUrl) { - console.log(`[Proxy] Resolving URL for ${cacheKey}...`); - playUrl = await fetchFreshUrl(source, id, quality); - } - if (!playUrl) { - res.writeHead(500); - res.end("Failed to Obtain URL"); - return; - } - const maxAttempts = 3; - let currentAttempt = 0; - let lastError = null; - while (currentAttempt < maxAttempts) { - try { - await proxyAndCache(req, res, playUrl, cacheFilePath, cacheKey); - break; - } catch (e) { - lastError = e; - currentAttempt++; - console.warn(`[Proxy] Proxy error (Attempt ${currentAttempt}/${maxAttempts}):`, e); - if (currentAttempt < maxAttempts) { - console.log("[Proxy] Error occurred, refreshing URL from plugin..."); - urlCache.delete(cacheKey); - const newUrl = await fetchFreshUrl(source, id, quality); - if (newUrl) { - playUrl = newUrl; - continue; - } else { - console.error("[Proxy] Failed to refresh URL"); - } - } - if (!res.headersSent) { - res.writeHead((lastError == null ? void 0 : lastError.statusCode) || 502); - res.end("Proxy Error"); - } - break; - } - } - } catch (err) { - console.error("[Proxy] Internal Error:", err); - if (!res.headersSent) { - res.writeHead(500); - res.end("Internal Server Error"); - } - } - }); - server.listen(PORT, "127.0.0.1", () => { - ensureCacheDir(); - cleanupTempFiles(); - console.log(`[Proxy] Server running at http://127.0.0.1:${PORT}/music`); - console.log(`[Proxy] Cache dir: ${CACHE_DIR}`); - }); - return server; -} -const DEFAULT_SETTINGS = { - persistCache: true, - theme: "dark", - accentColor: "#ec4141" - // Default red -}; -let settingsCache = null; -function getSettingsPath() { - return path.join(app.getPath("userData"), "settings.json"); -} -function loadSettings() { - if (settingsCache) return settingsCache; - const settingsPath = getSettingsPath(); - try { - if (fs.existsSync(settingsPath)) { - const data = fs.readFileSync(settingsPath, "utf-8"); - settingsCache = { ...DEFAULT_SETTINGS, ...JSON.parse(data) }; - console.log("[Settings] Loaded from disk:", settingsCache); - return settingsCache; - } - } catch (e) { - console.error("[Settings] Failed to load settings:", e); - } - settingsCache = { ...DEFAULT_SETTINGS }; - return settingsCache; -} -function saveSettings(settings) { - settingsCache = { ...loadSettings(), ...settings }; - const settingsPath = getSettingsPath(); - try { - fs.writeFileSync(settingsPath, JSON.stringify(settingsCache, null, 2)); - console.log("[Settings] Saved to disk:", settingsCache); - } catch (e) { - console.error("[Settings] Failed to save settings:", e); - } - return settingsCache; -} -function getSetting(key) { - return loadSettings()[key]; -} -const require$1 = createRequire(import.meta.url); -const __dirname$1 = path$1.dirname(fileURLToPath(import.meta.url)); -process.env.APP_ROOT = path$1.join(__dirname$1, ".."); -const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; -const MAIN_DIST = path$1.join(process.env.APP_ROOT, "dist-electron"); -const RENDERER_DIST = path$1.join(process.env.APP_ROOT, "dist"); -process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path$1.join(process.env.APP_ROOT, "public") : RENDERER_DIST; -let win; -let qzplayer; -function createWindow() { - win = new BrowserWindow({ - frame: false, - minWidth: 950, - minHeight: 800, - width: 1e3, - height: 800, - icon: path$1.join(process.env.VITE_PUBLIC, "electron-vite.svg"), - webPreferences: { - preload: path$1.join(__dirname$1, "preload.mjs") - // nodeIntegration: false, - // contextIsolation: true, - } - }); - win.webContents.on("did-finish-load", () => { - win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString()); - }); - if (VITE_DEV_SERVER_URL) { - win.loadURL(VITE_DEV_SERVER_URL); - } else { - win.loadFile(path$1.join(RENDERER_DIST, "index.html")); - } - win.webContents.openDevTools(); - registerZoomShortcuts(win); -} -ipcMain.on("window-minimize", (event) => { - var _a; - return (_a = BrowserWindow.fromWebContents(event.sender)) == null ? void 0 : _a.minimize(); -}); -ipcMain.on("window-maximize", () => (win == null ? void 0 : win.isMaximized()) ? win.unmaximize() : win == null ? void 0 : win.maximize()); -ipcMain.on("window-close", () => win == null ? void 0 : win.close()); -ipcMain.handle("window-is-maximized", () => (win == null ? void 0 : win.isMaximized()) || false); -ipcMain.handle("qzplayer-command", async (_, command) => { - if (qzplayer) { - qzplayer.send(command); - } -}); -ipcMain.handle("qzplayer-load", (_, url) => qzplayer == null ? void 0 : qzplayer.load(url)); -ipcMain.handle("qzplayer-play", () => qzplayer == null ? void 0 : qzplayer.play()); -ipcMain.handle("qzplayer-pause", () => qzplayer == null ? void 0 : qzplayer.pause()); -ipcMain.handle("qzplayer-toggle-pause", () => qzplayer == null ? void 0 : qzplayer.togglePause()); -ipcMain.handle("qzplayer-stop", () => qzplayer == null ? void 0 : qzplayer.stop()); -ipcMain.handle("qzplayer-set-volume", (_, vol) => qzplayer == null ? void 0 : qzplayer.setVolume(vol)); -ipcMain.handle("qzplayer-seek", (_, time) => qzplayer == null ? void 0 : qzplayer.seek(time)); -ipcMain.handle( - "plugin:call", - async (_evenv, pluginId, method, args) => { - const plugin = new PluginSystem(pluginId); - if (typeof plugin[method] !== "function") { - return { - success: false, - error: `Method ${method} not found` - }; - } - return await plugin[method](...args); - } -); -ipcMain.handle("cache:getInfo", () => { - const settings = loadSettings(); - return { - path: getCacheDir(), - size: getCacheSize(), - persistCache: settings.persistCache - }; -}); -ipcMain.handle("cache:setPersist", (_, persist) => { - setPersistCache(persist); - saveSettings({ persistCache: persist }); -}); -ipcMain.handle("cache:openFolder", () => { - const dir = getCacheDir(); - require$1("electron").shell.openPath(dir); -}); -ipcMain.handle("cache:clear", () => { - clearCacheNow(); -}); -ipcMain.handle("settings:getAll", () => { - return loadSettings(); -}); -ipcMain.handle("settings:set", (_, settings) => { - return saveSettings(settings); -}); -ipcMain.handle("settings:getTheme", () => { - return getSetting("theme"); -}); -ipcMain.handle("settings:setTheme", (_, theme) => { - saveSettings({ theme }); -}); -ipcMain.handle("settings:getAccentColor", () => { - return getSetting("accentColor"); -}); -ipcMain.handle("settings:setAccentColor", (_, color) => { - saveSettings({ accentColor: color }); -}); -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - win = null; - } -}); -app.on("will-quit", () => { - cleanupCache(); - if (qzplayer) { - qzplayer.destroy(); - } -}); -function registerZoomShortcuts(win2) { - win2.webContents.on("before-input-event", (event, input) => { - if (input.control || input.meta) { - if (input.key.toLowerCase() === "=" || input.key === "+") { - let currentZoom = win2.webContents.getZoomFactor(); - win2.webContents.setZoomFactor(currentZoom + 0.1); - event.preventDefault(); - } else if (input.key === "-" || input.key === "_") { - let currentZoom = win2.webContents.getZoomFactor(); - if (currentZoom > 0.5) { - win2.webContents.setZoomFactor(currentZoom - 0.1); - } - event.preventDefault(); - } else if (input.key === "0") { - win2.webContents.setZoomFactor(1); - event.preventDefault(); - } - } - }); -} -app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } -}); -app.whenReady().then(() => { - const pluginsPath = path$1.join(app.getPath("userData"), "plugins"); - if (!fs$1.existsSync(pluginsPath)) { - fs$1.mkdirSync(pluginsPath, { recursive: true }); - } - const wyPluginPath = path$1.join(pluginsPath, "wy"); - const wyPluginIndex = path$1.join(wyPluginPath, "index.js"); - if (!fs$1.existsSync(wyPluginIndex)) { - if (!fs$1.existsSync(wyPluginPath)) fs$1.mkdirSync(wyPluginPath, { recursive: true }); - fs$1.writeFileSync(wyPluginIndex, ` -module.exports = { - async getUrl(id, quality) { - const url = \`https://api.qz.shiqianjiang.cn/music/url?source=wy&songId=\${id}&quality=\${quality}&key=testkey\`; - try { - const response = await fetch(url); - const data = await response.json(); - return data; - } catch (e) { - return { success: false, error: e.message }; - } - } -} - `.trim()); - } - Menu.setApplicationMenu(null); - createWindow(); - startProxyServer(); - qzplayer = new QzpController(); - qzplayer.start(); - qzplayer.on("event", (data) => { - if (win && !win.isDestroyed()) { - win.webContents.send("qzplayer-event", data); - } - }); -}); -export { - MAIN_DIST, - RENDERER_DIST, - VITE_DEV_SERVER_URL -}; diff --git a/dist-electron/preload.mjs b/dist-electron/preload.mjs deleted file mode 100644 index b548e37..0000000 --- a/dist-electron/preload.mjs +++ /dev/null @@ -1,39 +0,0 @@ -"use strict"; -const electron = require("electron"); -electron.contextBridge.exposeInMainWorld("electronAPI", { - // 窗口控制 - minimizeWindow: () => electron.ipcRenderer.send("window-minimize"), - maximizeWindow: () => electron.ipcRenderer.send("window-maximize"), - closeWindow: () => electron.ipcRenderer.send("window-close"), - isMaximized: () => electron.ipcRenderer.invoke("window-is-maximized"), - // qzplayer Control - qzplayer: { - load: (url) => electron.ipcRenderer.invoke("qzplayer-load", url), - play: () => electron.ipcRenderer.invoke("qzplayer-play"), - pause: () => electron.ipcRenderer.invoke("qzplayer-pause"), - togglePause: () => electron.ipcRenderer.invoke("qzplayer-toggle-pause"), - stop: () => electron.ipcRenderer.invoke("qzplayer-stop"), - setVolume: (vol) => electron.ipcRenderer.invoke("qzplayer-set-volume", vol), - seek: (time) => electron.ipcRenderer.invoke("qzplayer-seek", time), - onEvent: (callback) => electron.ipcRenderer.on("qzplayer-event", callback) - }, - // Plugin System - plugin: { - call: (pluginId, method, args) => electron.ipcRenderer.invoke("plugin:call", pluginId, method, args), - search: (pluginId, query, page, limit) => electron.ipcRenderer.invoke("plugin:call", pluginId, "search", [query, page, limit]) - }, - // Cache Control - getCacheInfo: () => electron.ipcRenderer.invoke("cache:getInfo"), - setCachePersist: (persist) => electron.ipcRenderer.invoke("cache:setPersist", persist), - openCacheFolder: () => electron.ipcRenderer.invoke("cache:openFolder"), - clearCache: () => electron.ipcRenderer.invoke("cache:clear"), - // Settings - settings: { - getAll: () => electron.ipcRenderer.invoke("settings:getAll"), - set: (settings) => electron.ipcRenderer.invoke("settings:set", settings), - getTheme: () => electron.ipcRenderer.invoke("settings:getTheme"), - setTheme: (theme) => electron.ipcRenderer.invoke("settings:setTheme", theme), - getAccentColor: () => electron.ipcRenderer.invoke("settings:getAccentColor"), - setAccentColor: (color) => electron.ipcRenderer.invoke("settings:setAccentColor", color) - } -}); diff --git a/electron-builder.json5 b/electron-builder.json5 deleted file mode 100644 index d8e386b..0000000 --- a/electron-builder.json5 +++ /dev/null @@ -1,43 +0,0 @@ -// @see - https://www.electron.build/configuration/configuration -{ - "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", - "appId": "YourAppID", - "asar": true, - "productName": "YourAppName", - "directories": { - "output": "release/${version}" - }, - "files": [ - "dist", - "dist-electron" - ], - "mac": { - "target": [ - "dmg" - ], - "artifactName": "${productName}-Mac-${version}-Installer.${ext}" - }, - "win": { - "target": [ - { - "target": "nsis", - "arch": [ - "x64" - ] - } - ], - "artifactName": "${productName}-Windows-${version}-Setup.${ext}" - }, - "nsis": { - "oneClick": false, - "perMachine": false, - "allowToChangeInstallationDirectory": true, - "deleteAppDataOnUninstall": false - }, - "linux": { - "target": [ - "AppImage" - ], - "artifactName": "${productName}-Linux-${version}.${ext}" - } -} diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..dc162b2 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,29 @@ +import { resolve } from 'path' +import { defineConfig } from 'electron-vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import wasm from 'vite-plugin-wasm' + +export default defineConfig({ + main: { + }, + preload: { + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve('src/renderer/src'), + '@assets': resolve('src/renderer/assets') + } + }, + plugins: [vue(), vueJsx(), wasm()], + build: { + rollupOptions: { + input: resolve(__dirname, 'src/renderer/index.html') + } + }, + server: { + host: "0.0.0.0" + } + } +}) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts deleted file mode 100644 index f3b4f4a..0000000 --- a/electron/electron-env.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -declare namespace NodeJS { - interface ProcessEnv { - /** - * The built directory structure - * - * ```tree - * ├─┬─┬ dist - * │ │ └── index.html - * │ │ - * │ ├─┬ dist-electron - * │ │ ├── main.js - * │ │ └── preload.js - * │ - * ``` - */ - APP_ROOT: string - /** /dist/ or /public/ */ - VITE_PUBLIC: string - } -} - -// Used in Renderer process, expose in `preload.ts` -interface Window { - ipcRenderer: import('electron').IpcRenderer -} diff --git a/electron/main.ts b/electron/main.ts deleted file mode 100644 index d6a4c93..0000000 --- a/electron/main.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { app, BrowserWindow, Menu, ipcMain } from 'electron' -import { createRequire } from 'node:module' -import { fileURLToPath } from 'node:url' -import path from 'node:path' -import fs from 'node:fs' -import { QzpController } from './qzpController.ts' -import { startProxyServer, cleanupCache, getCacheDir, getCacheSize, setPersistCache, clearCacheNow } from './proxyServer' -import { PluginSystem } from '../src/main/pluginSystem.ts' -import { loadSettings, saveSettings, getSetting, AppSettings } from './settingsStore' -// @ts-ignore -const require = createRequire(import.meta.url) -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -process.env.APP_ROOT = path.join(__dirname, '..') - -export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'] -export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron') -export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist') - -process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST - -let win: BrowserWindow | null -let qzplayer: QzpController | null - -// === Electron 窗口逻辑 === - -function createWindow() { - win = new BrowserWindow({ - frame: false, - minWidth: 950, - minHeight: 800, - width: 1000, - height: 800, - icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), - webPreferences: { - preload: path.join(__dirname, 'preload.mjs'), - // nodeIntegration: false, - // contextIsolation: true, - }, - }) - - win.webContents.on('did-finish-load', () => { - win?.webContents.send('main-process-message', new Date().toLocaleString()) - }) - - if (VITE_DEV_SERVER_URL) { - win.loadURL(VITE_DEV_SERVER_URL) - } else { - win.loadFile(path.join(RENDERER_DIST, 'index.html')) - } - win.webContents.openDevTools(); - registerZoomShortcuts(win) -} - -// === IPC 监听 === - -ipcMain.on('window-minimize', (event) => BrowserWindow.fromWebContents(event.sender)?.minimize()) -ipcMain.on('window-maximize', () => win?.isMaximized() ? win.unmaximize() : win?.maximize()) -ipcMain.on('window-close', () => win?.close()) -ipcMain.handle('window-is-maximized', () => win?.isMaximized() || false) - -// --- qzplayer IPC Handlers --- -ipcMain.handle('qzplayer-command', async (_, command: any[]) => { - if (qzplayer) { - qzplayer.send(command) - } -}) - -// Quick Helpers -ipcMain.handle('qzplayer-load', (_, url) => qzplayer?.load(url)) -ipcMain.handle('qzplayer-play', () => qzplayer?.play()) -ipcMain.handle('qzplayer-pause', () => qzplayer?.pause()) -ipcMain.handle('qzplayer-toggle-pause', () => qzplayer?.togglePause()) -ipcMain.handle('qzplayer-stop', () => qzplayer?.stop()) -ipcMain.handle('qzplayer-set-volume', (_, vol) => qzplayer?.setVolume(vol)) -ipcMain.handle('qzplayer-seek', (_, time) => qzplayer?.seek(time)) - -// PluginSystem -ipcMain.handle( - 'plugin:call', - async (_evenv, pluginId: string, method: string, args: any[]) => { - const plugin = new PluginSystem(pluginId) - - if (typeof (plugin as any)[method] !== 'function') { - return { - success: false, - error: `Method ${method} not found` - } - } - - return await (plugin as any)[method](...args) - } -) - - - -// Cache IPC Handlers -ipcMain.handle('cache:getInfo', () => { - const settings = loadSettings(); - return { - path: getCacheDir(), - size: getCacheSize(), - persistCache: settings.persistCache - } -}) - -ipcMain.handle('cache:setPersist', (_, persist: boolean) => { - setPersistCache(persist) - saveSettings({ persistCache: persist }) -}) - -ipcMain.handle('cache:openFolder', () => { - const dir = getCacheDir() - require('electron').shell.openPath(dir) -}) - -ipcMain.handle('cache:clear', () => { - clearCacheNow() -}) - -// Settings IPC Handlers -ipcMain.handle('settings:getAll', () => { - return loadSettings() -}) - -ipcMain.handle('settings:set', (_, settings: Partial) => { - return saveSettings(settings) -}) - -ipcMain.handle('settings:getTheme', () => { - return getSetting('theme') -}) - -ipcMain.handle('settings:setTheme', (_, theme: 'dark' | 'light') => { - saveSettings({ theme }) -}) - -ipcMain.handle('settings:getAccentColor', () => { - return getSetting('accentColor') -}) - -ipcMain.handle('settings:setAccentColor', (_, color: string) => { - saveSettings({ accentColor: color }) -}) - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() - win = null - } -}) - -app.on('will-quit', () => { - cleanupCache() - if (qzplayer) { - qzplayer.destroy() - } -}) - -function registerZoomShortcuts(win: BrowserWindow) { - win.webContents.on('before-input-event', (event, input) => { - if (input.control || input.meta) { - if (input.key.toLowerCase() === '=' || input.key === '+') { - let currentZoom = win.webContents.getZoomFactor(); - win.webContents.setZoomFactor(currentZoom + 0.1); - event.preventDefault(); - } else if (input.key === '-' || input.key === '_') { - let currentZoom = win.webContents.getZoomFactor(); - // Limit minimum zoom to avoid making it too small to see - if (currentZoom > 0.5) { - win.webContents.setZoomFactor(currentZoom - 0.1); - } - event.preventDefault(); - } else if (input.key === '0') { - win.webContents.setZoomFactor(1); - event.preventDefault(); - } - } - }); -} - -app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } -}) - -// Test -app.whenReady().then(() => { - // Ensure plugins directory exists - const pluginsPath = path.join(app.getPath('userData'), 'plugins') - if (!fs.existsSync(pluginsPath)) { - fs.mkdirSync(pluginsPath, { recursive: true }) - } - - // --- Ensure Sample 'wy' Plugin Exists --- - const wyPluginPath = path.join(pluginsPath, 'wy') - const wyPluginIndex = path.join(wyPluginPath, 'index.js') - if (!fs.existsSync(wyPluginIndex)) { - if (!fs.existsSync(wyPluginPath)) fs.mkdirSync(wyPluginPath, { recursive: true }) - fs.writeFileSync(wyPluginIndex, ` -module.exports = { - async getUrl(id, quality) { - const url = \`https://api.qz.shiqianjiang.cn/music/url?source=wy&songId=\${id}&quality=\${quality}&key=testkey\`; - try { - const response = await fetch(url); - const data = await response.json(); - return data; - } catch (e) { - return { success: false, error: e.message }; - } - } -} - `.trim()) - } - // ---------------------------------------- - - Menu.setApplicationMenu(null) - createWindow() - - // Start Proxy Server - startProxyServer() - - // Start qzplayer - qzplayer = new QzpController() - qzplayer.start() - - qzplayer.on('event', (data) => { - // Forward qzplayer events to Render Process - if (win && !win.isDestroyed()) { - win.webContents.send('qzplayer-event', data) - } - }) -}) diff --git a/electron/preload.ts b/electron/preload.ts deleted file mode 100644 index 20eb66d..0000000 --- a/electron/preload.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { contextBridge, ipcRenderer } from 'electron' - -contextBridge.exposeInMainWorld('electronAPI', { - // 窗口控制 - minimizeWindow: () => ipcRenderer.send('window-minimize'), - maximizeWindow: () => ipcRenderer.send('window-maximize'), - closeWindow: () => ipcRenderer.send('window-close'), - isMaximized: () => ipcRenderer.invoke('window-is-maximized'), - - // qzplayer Control - qzplayer: { - load: (url: string) => ipcRenderer.invoke('qzplayer-load', url), - play: () => ipcRenderer.invoke('qzplayer-play'), - pause: () => ipcRenderer.invoke('qzplayer-pause'), - togglePause: () => ipcRenderer.invoke('qzplayer-toggle-pause'), - stop: () => ipcRenderer.invoke('qzplayer-stop'), - setVolume: (vol: number) => ipcRenderer.invoke('qzplayer-set-volume', vol), - seek: (time: number) => ipcRenderer.invoke('qzplayer-seek', time), - onEvent: (callback: (event: any, data: any) => void) => ipcRenderer.on('qzplayer-event', callback) - }, - - // Plugin System - plugin: { - call: (pluginId: string, method: string, args: any[]) => ipcRenderer.invoke('plugin:call', pluginId, method, args), - search: (pluginId: string, query: string, page: number, limit: number) => ipcRenderer.invoke('plugin:call', pluginId, 'search', [query, page, limit]), - }, - - // Cache Control - getCacheInfo: () => ipcRenderer.invoke('cache:getInfo'), - setCachePersist: (persist: boolean) => ipcRenderer.invoke('cache:setPersist', persist), - openCacheFolder: () => ipcRenderer.invoke('cache:openFolder'), - clearCache: () => ipcRenderer.invoke('cache:clear'), - - // Settings - settings: { - getAll: () => ipcRenderer.invoke('settings:getAll'), - set: (settings: any) => ipcRenderer.invoke('settings:set', settings), - getTheme: () => ipcRenderer.invoke('settings:getTheme'), - setTheme: (theme: 'dark' | 'light') => ipcRenderer.invoke('settings:setTheme', theme), - getAccentColor: () => ipcRenderer.invoke('settings:getAccentColor'), - setAccentColor: (color: string) => ipcRenderer.invoke('settings:setAccentColor', color) - } -}) \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index ba7d41f..0000000 --- a/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + Vue + TS - - -
- - - diff --git a/package-lock.json b/package-lock.json index 4c8e093..f628e09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@applemusic-like-lyrics/core": "^0.2.0", "@applemusic-like-lyrics/lyric": "^0.3.0", "@applemusic-like-lyrics/vue": "^0.2.0", + "@electron-toolkit/tsconfig": "^2.0.0", "@iconify/vue": "^5.0.0", "@pixi/app": "^7.4.3", "@pixi/core": "^7.4.3", @@ -26,7 +27,6 @@ "pinia": "^3.0.4", "tdesign-vue-next": "^1.17.7", "url": "^0.11.4", - "vite-plugin-electron-renderer": "^0.14.6", "vite-plugin-wasm": "^3.5.0", "vue": "^3.4.21", "vue-router": "^4.6.4" @@ -35,10 +35,10 @@ "@vitejs/plugin-vue": "^5.0.4", "electron": "^30.0.1", "electron-builder": "^24.13.3", + "electron-vite": "^5.0.0", "sass-embedded": "^1.97.1", "typescript": "^5.2.2", "vite": "^5.1.6", - "vite-plugin-electron": "^0.28.6", "vite-plugin-node-polyfills": "^0.25.0", "vue-tsc": "^2.0.26" } @@ -406,6 +406,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", @@ -513,6 +529,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/@electron-toolkit/tsconfig": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@electron-toolkit/tsconfig/-/tsconfig-2.0.0.tgz", + "integrity": "sha512-AdPsP770WhW7b260h13SHMdmjEEHJL6xFtgi3jwgdsSQbJOkJLeNnnpZW9qxTPCvmRI6vmdzWz5K3gibFS6SNg==", + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + } + }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz", @@ -1067,6 +1092,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -1083,6 +1125,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -1099,6 +1158,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -2393,7 +2469,6 @@ "version": "20.19.27", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.27.tgz", "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3694,6 +3769,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -4815,6 +4900,469 @@ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "license": "ISC" }, + "node_modules/electron-vite": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/electron-vite/-/electron-vite-5.0.0.tgz", + "integrity": "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "cac": "^6.7.14", + "esbuild": "^0.25.11", + "magic-string": "^0.30.19", + "picocolors": "^1.1.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron-vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/electron-vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/element-plus": { "version": "2.13.0", "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.0.tgz", @@ -8764,7 +9312,6 @@ "version": "6.21.0", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/universalify": { @@ -8955,27 +9502,6 @@ } } }, - "node_modules/vite-plugin-electron": { - "version": "0.28.8", - "resolved": "https://registry.npmmirror.com/vite-plugin-electron/-/vite-plugin-electron-0.28.8.tgz", - "integrity": "sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite-plugin-electron-renderer": "*" - }, - "peerDependenciesMeta": { - "vite-plugin-electron-renderer": { - "optional": true - } - } - }, - "node_modules/vite-plugin-electron-renderer": { - "version": "0.14.6", - "resolved": "https://registry.npmmirror.com/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", - "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", - "license": "MIT" - }, "node_modules/vite-plugin-node-polyfills": { "version": "0.25.0", "resolved": "https://registry.npmmirror.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.25.0.tgz", diff --git a/package.json b/package.json index 73c55e9..951b231 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,16 @@ "name": "qzmusic", "private": true, "version": "0.0.0", - "type": "module", "scripts": { - "dev": "vite", - "build": "vue-tsc && vite build && electron-builder", - "preview": "vite preview" + "dev": "electron-vite dev", + "build": "electron-vite build", + "preview": "electron-vite preview" }, "dependencies": { "@applemusic-like-lyrics/core": "^0.2.0", "@applemusic-like-lyrics/lyric": "^0.3.0", "@applemusic-like-lyrics/vue": "^0.2.0", + "@electron-toolkit/tsconfig": "^2.0.0", "@iconify/vue": "^5.0.0", "@pixi/app": "^7.4.3", "@pixi/core": "^7.4.3", @@ -27,7 +27,6 @@ "pinia": "^3.0.4", "tdesign-vue-next": "^1.17.7", "url": "^0.11.4", - "vite-plugin-electron-renderer": "^0.14.6", "vite-plugin-wasm": "^3.5.0", "vue": "^3.4.21", "vue-router": "^4.6.4" @@ -36,12 +35,12 @@ "@vitejs/plugin-vue": "^5.0.4", "electron": "^30.0.1", "electron-builder": "^24.13.3", + "electron-vite": "^5.0.0", "sass-embedded": "^1.97.1", "typescript": "^5.2.2", "vite": "^5.1.6", - "vite-plugin-electron": "^0.28.6", "vite-plugin-node-polyfills": "^0.25.0", "vue-tsc": "^2.0.26" }, - "main": "dist-electron/main.js" + "main": "out/main/index.js" } diff --git a/src/main/pluginSystem.ts b/src/main/pluginSystem.ts index 47684ba..3c1e314 100644 --- a/src/main/pluginSystem.ts +++ b/src/main/pluginSystem.ts @@ -2,6 +2,7 @@ import { app } from 'electron' import path from 'path' import fs from 'fs' import { createRequire } from 'node:module' +import { MessagePlugin } from "tdesign-vue-next"; const require = createRequire(import.meta.url) @@ -15,7 +16,8 @@ type PluginModule = { getUrl?: (id: string, quality: string) => Promise | string, musicSearch?: { search: (query: string, page: number, limit: number) => Promise | any - } + }, + getLyric?: (id: string) => Promise | object } export class PluginSystem { @@ -26,27 +28,6 @@ export class PluginSystem { this.loadPlugin() } - async search(query: string, page: number, limit: number): Promise { - if (!this.plugin?.musicSearch?.search) { - return { - list: [], - total: 0, - allPage: 0, - error: 'Search not implemented' - } - } - try { - return await this.plugin.musicSearch.search(query, page, limit) - } catch (e: any) { - return { - list: [], - total: 0, - allPage: 0, - error: e.message || 'Plugin search error' - } - } - } - private loadPlugin() { try { const pluginPath = path.join( @@ -100,4 +81,42 @@ export class PluginSystem { } } } + + async search(query: string, page: number, limit: number): Promise { + if (!this.plugin?.musicSearch?.search) { + return { + list: [], + total: 0, + allPage: 0, + error: 'Search not implemented' + } + } + try { + return await this.plugin.musicSearch.search(query, page, limit) + } catch (e: any) { + return { + list: [], + total: 0, + allPage: 0, + error: e.message || 'Plugin search error' + } + } + } + + async getLyric(id: string): Promise { + console.log("getLyric not implemented"); + if (!this.plugin?.getLyric) { + console.log("getLyric not implemented2"); + return + } + try { + const result = await this.plugin.getLyric(id) + console.log(result) + return result + } catch (e: any) { + console.log(e) + MessagePlugin.error(e).then() + return {} + } + } } \ No newline at end of file diff --git a/electron/proxyServer.ts b/src/main/proxyServer.ts similarity index 99% rename from electron/proxyServer.ts rename to src/main/proxyServer.ts index bee3e3e..c92c183 100644 --- a/electron/proxyServer.ts +++ b/src/main/proxyServer.ts @@ -4,7 +4,7 @@ import fs from 'fs'; import path from 'path'; import { app } from 'electron'; // @ts-ignore -import { PluginSystem } from '../src/main/pluginSystem.ts'; +import { PluginSystem } from './pluginSystem.ts'; const PORT = 5266; let CACHE_DIR = ''; diff --git a/electron/qzpController.ts b/src/main/qzpController.ts similarity index 98% rename from electron/qzpController.ts rename to src/main/qzpController.ts index 9ef4578..7ddb15d 100644 --- a/electron/qzpController.ts +++ b/src/main/qzpController.ts @@ -97,7 +97,7 @@ export class QzpController extends EventEmitter { for (const msg of messages) { if (!msg.trim()) continue; - console.log('[IPC]', msg); // User requested raw communication + //console.log('[IPC]', msg); // User requested raw communication try { const json = JSON.parse(msg); this.emit('message', json); diff --git a/electron/settingsStore.ts b/src/main/settingsStore.ts similarity index 100% rename from electron/settingsStore.ts rename to src/main/settingsStore.ts diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..ce4e985 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,16 @@ + + + + + + + + Vite + Vue + TS + + + +
+ + + + \ No newline at end of file diff --git a/src/renderer/App.vue b/src/renderer/src/App.vue similarity index 96% rename from src/renderer/App.vue rename to src/renderer/src/App.vue index c6ad75e..33f18a6 100644 --- a/src/renderer/App.vue +++ b/src/renderer/src/App.vue @@ -26,5 +26,5 @@ onMounted(async () => { \ No newline at end of file diff --git a/src/renderer/components/FullScreenPlayer.vue b/src/renderer/src/components/FullScreenPlayer.vue similarity index 98% rename from src/renderer/components/FullScreenPlayer.vue rename to src/renderer/src/components/FullScreenPlayer.vue index 9ed6a85..cd55ccf 100644 --- a/src/renderer/components/FullScreenPlayer.vue +++ b/src/renderer/src/components/FullScreenPlayer.vue @@ -234,6 +234,7 @@ const drawSpectrum = () => { // Adjust canvas size const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; @@ -241,7 +242,7 @@ const drawSpectrum = () => { const width = rect.width; const height = rect.height; - + ctx.clearRect(0, 0, width, height); const targetData = playerStore.spectrum; @@ -251,8 +252,8 @@ const drawSpectrum = () => { // Adjust speed (0.1 - 0.3) for desired smoothness const lerpSpeed = 0.15; for (let i = 0; i < 32; i++) { - const target = targetData[i] || 0; - currentData[i] = currentData[i] + (target - currentData[i]) * lerpSpeed; + const target = targetData[i] || 0; + currentData[i] = currentData[i] + (target - currentData[i]) * lerpSpeed; } // 2. Draw Smooth Wave @@ -260,7 +261,7 @@ const drawSpectrum = () => { ctx.moveTo(0, height); const pointCount = currentData.length; - // Use a subset if desired, but 32 is fine. + // Use a subset if desired, but 32 is fine. // We want the wave to be centered or span the width. const step = width / (pointCount - 1); @@ -272,14 +273,14 @@ const drawSpectrum = () => { for (let i = 0; i < pointCount - 1; i++) { const xCurr = i * step; const yCurr = height - (currentData[i] * height * 0.5); - + const xNext = (i + 1) * step; const yNext = height - (currentData[i + 1] * height * 0.5); - + // Control point for quadratic curve (midpoint) const xMid = (xCurr + xNext) / 2; const yMid = (yCurr + yNext) / 2; - + ctx.quadraticCurveTo(xCurr, yCurr, xMid, yMid); } @@ -296,13 +297,13 @@ const drawSpectrum = () => { const gradient = ctx.createLinearGradient(0, height - 200, 0, height); gradient.addColorStop(0, 'rgba(255, 255, 255, 0.4)'); gradient.addColorStop(1, 'rgba(255, 255, 255, 0.05)'); - + ctx.fillStyle = gradient; - + // Add blur effect ctx.shadowBlur = 20; ctx.shadowColor = 'rgba(255, 255, 255, 0.5)'; - + ctx.fill(); // Reset shadow for next frame (performance) diff --git a/src/renderer/components/PlayerBar.vue b/src/renderer/src/components/PlayerBar.vue similarity index 99% rename from src/renderer/components/PlayerBar.vue rename to src/renderer/src/components/PlayerBar.vue index 13d2223..af7cee6 100644 --- a/src/renderer/components/PlayerBar.vue +++ b/src/renderer/src/components/PlayerBar.vue @@ -179,7 +179,8 @@ const handleBarClick = (e: MouseEvent) => { }; // Utils -const formatTime = (seconds: number) => { +const formatTime = (seconds2: number) => { + const seconds = Math.floor(seconds2 / 1000); if (!seconds || isNaN(seconds)) return '00:00'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); diff --git a/src/renderer/components/Settings.vue b/src/renderer/src/components/Settings.vue similarity index 100% rename from src/renderer/components/Settings.vue rename to src/renderer/src/components/Settings.vue diff --git a/src/renderer/components/Sidebar.vue b/src/renderer/src/components/Sidebar.vue similarity index 100% rename from src/renderer/components/Sidebar.vue rename to src/renderer/src/components/Sidebar.vue diff --git a/src/renderer/components/TopBar.vue b/src/renderer/src/components/TopBar.vue similarity index 100% rename from src/renderer/components/TopBar.vue rename to src/renderer/src/components/TopBar.vue diff --git a/src/renderer/layout/MainLayout.vue b/src/renderer/src/layout/MainLayout.vue similarity index 93% rename from src/renderer/layout/MainLayout.vue rename to src/renderer/src/layout/MainLayout.vue index 8810b06..c7088ce 100644 --- a/src/renderer/layout/MainLayout.vue +++ b/src/renderer/src/layout/MainLayout.vue @@ -27,7 +27,6 @@ const hasSongs = computed(() => playerStore.playlist.length > 0);