Skip to content

feat: Plugins#8

Merged
imsyy merged 17 commits intodevfrom
plugins
Apr 23, 2026
Merged

feat: Plugins#8
imsyy merged 17 commits intodevfrom
plugins

Conversation

@imsyy
Copy link
Copy Markdown
Collaborator

@imsyy imsyy commented Apr 22, 2026

No description provided.

imsyy and others added 12 commits April 20, 2026 10:00
- 清理 NavHeader 与 core/index.ts 的冗余注释
- 全局禁用 a 标签的拖拽,消除 hover 时的拖影

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 隔离:utilityProcess + vm.createContext 双层沙箱;心跳检测、崩溃自动重启(指数退避 3 次)
- HostApi:request(Electron net.fetch + URL 白名单)/ storage(每插件 KV 原子写)/ log / register / on
- lx 兼容:window.lx 垫片 + gz_ 前缀自动解压,沿用头部 JSDoc 元数据约定
- 路由:search / musicUrl 已端到端;lyric / pic / meta HostApi 已预留
- 渲染端:设置页新增「插件管理」分类,支持原生文件选择框导入、启用开关、卸载确认、状态徽章、lx/源标识
- SettingsItem 支持 fullWidth 模式以承载独占布局的自定义组件
- 打包:electron-vite 配置增补 sandbox.worker 独立 entry
- 详见 docs/plugins-mvp.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 22, 2026 10:31
@imsyy imsyy marked this pull request as draft April 22, 2026 10:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 旨在为 SPlayer-Next 引入插件系统(含主进程沙箱、IPC、渲染端管理 UI),并新增/重构多平台音源 API(Netease / QQMusic / Kugou)调用链路与部分类型结构。

Changes:

  • 新增插件系统:主进程 registry/router/sandbox/host/net/storage/loader + preload/IPC + 设置页插件管理 UI 与文档。
  • 新增统一音源 API IPC(apis:call / apis:clearSession)及渲染端代理(src/apis/*),并引入 Netease cookies 落库(SQLite account_sessions)。
  • 重构歌词类型(LyricSource/LyricData 等)与外部歌词列表结构,补充相关 i18n/样式与窗口/托盘行为调整。

Reviewed changes

Copilot reviewed 121 out of 125 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tsconfig.node.json 移除 electron/server 编译包含与路径别名。
src/utils/url.ts 新增外链判断/打开工具函数。
src/utils/lyric/parseTimeline.ts 更新 YRC 注释文案表述。
src/utils/lyric/parse.ts 调整外部歌词最优索引选择的入参类型。
src/types/settings-schema.ts 为设置项新增 fullWidth 支持。
src/styles/global.css 调整可拖拽行为与滚动条宽度。
src/stores/user.ts 新增用户登录态 store(Netease)。
src/stores/plugins.ts 新增插件管理 Pinia store(列表/订阅/安装/启用/卸载)。
src/stores/media.ts 将歌词来源类型切换为 LyricData 并适配字段名。
src/settings/schema.ts 新增“插件管理”设置分类与自定义面板组件。
src/services/lyricLoader.ts 歌词加载逻辑适配 LyricData
src/pages/Home.vue 增加 Netease 搜索测试相关 UI/逻辑。
src/layouts/components/NavHeader.vue 调整标题栏按钮属性与拖拽区域 class 分布。
src/i18n/locales/zh-CN.json 新增插件管理相关文案。
src/i18n/locales/en-US.json 新增插件管理相关文案(英文)。
src/components/ui/SToast.vue 调整 toast 容器 z-index。
src/components/settings/custom/PluginManager.vue 新增插件管理面板(导入/启用/卸载/更新提示)。
src/components/settings/SettingsItem.vue 支持 fullWidth 的 custom 设置项渲染分支。
src/apis/qqmusic.ts 新增 QQMusic 渲染端 API 代理(Proxy → IPC)。
src/apis/netease.ts 新增 Netease 渲染端 API 代理与清会话方法。
src/apis/kugou.ts 新增 Kugou 渲染端 API 代理(Proxy → IPC)。
shared/types/settings.ts 系统配置新增 plugins: PluginsConfig
shared/types/plugin.ts 新增插件系统跨进程类型契约与渲染端 PluginsApi。
shared/types/player.ts 扩展 TrackSource=plugin、补充 plugin 字段、调整 externalLyrics 结构。
shared/types/platform.ts 新增平台类型(netease/qqmusic/kugou)。
shared/types/nowPlaying.ts NowPlaying payload/snapshot 的歌词来源类型改为 LyricData
shared/types/lyrics.ts 重构歌词来源为 LyricSource 字面量 + LyricData
shared/types/apis.ts 新增统一音源 API 的类型(ApiPlatform/ApisApi/ApiCallResponse)。
shared/defaults/settings.ts 默认系统配置补齐插件默认配置。
shared/defaults/plugin-api.ts 新增 Host API level、超时、限制与错误码、默认插件配置。
electron/preload/index.ts 暴露 window.api.pluginswindow.api.apis
electron/preload/index.d.ts 增加 plugins/apis 的 preload 类型声明。
electron/main/window/index.ts 调整歌词窗口恢复逻辑与 isWin 引用位置。
electron/main/services/tray.ts 托盘菜单与任务栏歌词同步逻辑适配 isWin 常量。
electron/main/services/nowPlaying.ts NowPlaying 的歌词来源类型改为 LyricData
electron/main/plugins/storage.ts 新增每插件隔离 KV 存储(落盘+原子写)。
electron/main/plugins/sandbox.ts 新增 utilityProcess 沙箱控制器(心跳/超时/消息路由)。
electron/main/plugins/router.ts 新增插件动作路由(当前实现 musicUrl)。
electron/main/plugins/registry.ts 新增插件注册表(扫描/启停/重启/状态广播/安装卸载)。
electron/main/plugins/net.ts 新增宿主网络代理(net.fetch + 白名单 + 超时)。
electron/main/plugins/lx-shim.ts 新增 lx user_api 兼容垫片。
electron/main/plugins/loader.ts 新增脚本加载器(gz_ 解压、JSDoc 元数据解析、ID 生成)。
electron/main/plugins/host.ts 新增 Host API dispatch(request/storage 等)。
electron/main/ipc/window.ts 任务栏歌词窗口 IPC 按平台(Windows)条件注册。
electron/main/ipc/plugin.ts 新增插件系统 IPC(list/install/uninstall/resolveUrl/status 广播等)。
electron/main/ipc/library.ts artist avatar 获取入口从 @server/* 切到 @main/apis/musicbrainz
electron/main/ipc/index.ts 注册新增的 plugin/apis IPC handlers。
electron/main/ipc/config.ts taskbarLyric 的副作用/广播在非 Windows 下跳过。
electron/main/ipc/apis.ts 新增统一音源 API IPC 分发与清会话入口。
electron/main/database/sessions.ts 新增第三方音源账号 cookies 的 SQLite 存取。
electron/main/database/index.ts 初始化 DB 时新增 account_sessions 表。
electron/main/core/index.ts App 启动初始化插件系统;退出时做平台条件化写入与 shutdown。
electron/main/apis/qqmusic/modules/song_list.ts 新增 QQMusic 歌单详情模块。
electron/main/apis/qqmusic/modules/song_info.ts 新增 QQMusic 单曲详情模块。
electron/main/apis/qqmusic/modules/search.ts 新增 QQMusic 搜索模块。
electron/main/apis/qqmusic/modules/match.ts 新增 QQMusic 模糊匹配歌词模块(search+lyric)。
electron/main/apis/qqmusic/modules/lyric.ts 新增 QQMusic 歌词模块(解密 QRC/LRC/译/罗马音)。
electron/main/apis/qqmusic/modules/leaderboard.ts 新增 QQMusic 排行榜模块。
electron/main/apis/qqmusic/modules/index.ts QQMusic 模块注册表。
electron/main/apis/qqmusic/modules/hot_search.ts 新增 QQMusic 热搜模块。
electron/main/apis/qqmusic/index.ts QQMusic 主进程服务入口(含内存缓存)。
electron/main/apis/qqmusic/core/types.ts QQMusic 模块函数签名类型。
electron/main/apis/qqmusic/core/request.ts QQMusic 请求层(session 缓存/初始化)。
electron/main/apis/qqmusic/core/qrc.ts QRC 解密与解压工具。
electron/main/apis/qqmusic/core/config.ts QQMusic 通用常量与 comm 伪装参数。
electron/main/apis/netease/modules/user_subcount.ts 新增 Netease 用户收藏计数模块。
electron/main/apis/netease/modules/user_record.ts 新增 Netease 听歌排行模块。
electron/main/apis/netease/modules/user_playlist.ts 新增 Netease 用户歌单列表模块。
electron/main/apis/netease/modules/user_level.ts 新增 Netease 用户等级模块。
electron/main/apis/netease/modules/user_follows.ts 新增 Netease 关注列表模块。
electron/main/apis/netease/modules/user_followeds.ts 新增 Netease 粉丝列表模块。
electron/main/apis/netease/modules/user_detail_new.ts 新增 Netease 用户详情(eapi)模块。
electron/main/apis/netease/modules/user_detail.ts 新增 Netease 用户详情(旧版)模块。
electron/main/apis/netease/modules/user_cloud.ts 新增 Netease 云盘列表模块。
electron/main/apis/netease/modules/user_account.ts 新增 Netease 当前账号模块。
electron/main/apis/netease/modules/search_suggest_pc.ts 新增 Netease PC 搜索建议模块。
electron/main/apis/netease/modules/search_suggest.ts 新增 Netease 搜索建议模块。
electron/main/apis/netease/modules/search_multimatch.ts 新增 Netease 多类型搜索建议模块。
electron/main/apis/netease/modules/search_match.ts 新增 Netease 本地歌曲匹配模块。
electron/main/apis/netease/modules/search_hot_detail.ts 新增 Netease 热搜详情模块。
electron/main/apis/netease/modules/search_hot.ts 新增 Netease 热搜简版模块。
electron/main/apis/netease/modules/search_default.ts 新增 Netease 默认搜索词模块。
electron/main/apis/netease/modules/search.ts 新增 Netease 普通搜索模块(含 voice 特例)。
electron/main/apis/netease/modules/register_anonimous.ts 新增 Netease 匿名注册模块。
electron/main/apis/netease/modules/logout.ts 新增 Netease 登出模块。
electron/main/apis/netease/modules/login_status.ts 新增 Netease 登录状态模块。
electron/main/apis/netease/modules/login_refresh.ts 新增 Netease 刷新登录态模块。
electron/main/apis/netease/modules/login_qr_key.ts 新增 Netease 二维码 key 模块。
electron/main/apis/netease/modules/login_qr_create.ts 新增 Netease 二维码 URL 生成模块。
electron/main/apis/netease/modules/login_qr_check.ts 新增 Netease 二维码轮询模块。
electron/main/apis/netease/modules/login_cellphone.ts 新增 Netease 手机号登录模块。
electron/main/apis/netease/modules/login.ts 新增 Netease 邮箱登录模块。
electron/main/apis/netease/modules/index.ts Netease 模块注册表。
electron/main/apis/netease/modules/cloudsearch.ts 新增 Netease cloudsearch 模块。
electron/main/apis/netease/modules/captcha_verify.ts 新增 Netease 短信验证码校验模块。
electron/main/apis/netease/modules/captcha_sent.ts 新增 Netease 发送短信验证码模块。
electron/main/apis/netease/index.ts Netease 主进程服务入口(cookies 落库 + 内存 cache)。
electron/main/apis/netease/core/types.ts Netease 模块函数签名类型。
electron/main/apis/netease/core/request.ts Netease 请求层(加密/UA/cookie/解密/状态归一化)。
electron/main/apis/netease/core/option.ts Netease 请求 options 工厂。
electron/main/apis/netease/core/device.ts Netease 进程级 deviceId/匿名 token 管理。
electron/main/apis/netease/core/crypto.ts Netease weapi/linuxapi/eapi 加解密实现。
electron/main/apis/netease/core/cookie.ts Netease cookie 字符串/对象互转。
electron/main/apis/netease/core/config.ts Netease 常量/UA/OS 伪装与加密相关配置。
electron/main/apis/netease/core/cache.ts Netease 内存响应缓存(LRU/TTL)。
electron/main/apis/musicbrainz.ts 新增 MusicBrainz/TheAudioDB 歌手头像抓取与缓存。
electron/main/apis/kugou/modules/search.ts 新增 Kugou 搜索模块(结果规范化)。
electron/main/apis/kugou/modules/lyric.ts 新增 Kugou 歌词模块(krc/lrc 解码)。
electron/main/apis/kugou/modules/index.ts Kugou 模块注册表。
electron/main/apis/kugou/index.ts Kugou 主进程服务入口(含内存缓存)。
electron/main/apis/kugou/core/types.ts Kugou 模块函数签名类型。
electron/main/apis/kugou/core/request.ts Kugou 请求层(GET + retry + 错误码处理)。
electron/main/apis/kugou/core/krc.ts KRC 解密与格式化工具。
electron/main/apis/kugou/core/config.ts Kugou 常量与工具(headers、实体解码、时长转换)。
electron.vite.config.ts main build 增加 sandbox.worker 入口并移除 @server alias。
docs/plugins-usage.md 新增插件使用指南文档。
docs/plugins-mvp.md 新增插件系统 MVP 进度/接续文档。
demo/audio-architecture.md 更新文档中 YRC 文案为 Netease。
components.d.ts 更新自动组件类型声明(新增图标与 PluginManager)。
CLAUDE.md 更新类型系统说明与文件指向。
.github/copilot-instructions.md 更新路径别名说明,移除 @server
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread src/utils/url.ts Outdated
Comment on lines +13 to +15
export const openExternal = (url?: string | null): void => {
if (!isExternalUrl(url)) return;
window.open(url, "_blank");
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openExternal 直接使用 window.open(url, "_blank") 会让新窗口拿到 window.opener,存在被外链页面反向控制当前窗口的风险。建议至少加上 noopener,noreferrer(并将 opener 置空),或在 Electron 环境下改为通过主进程 shell.openExternal 打开外链。

Copilot uses AI. Check for mistakes.
@imsyy imsyy marked this pull request as ready for review April 23, 2026 03:51
Copilot AI review requested due to automatic review settings April 23, 2026 03:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 123 out of 127 changed files in this pull request and generated 9 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread docs/plugins-usage.md Outdated

- 仅支持 HTTP/HTTPS
- 单次下载上限 **9 MB**
- 请求超时 15 秒,最多跟随 3 次重定向
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文档写“最多跟随 3 次重定向”,但当前 electron/main/ipc/plugin.ts 里用的是 net.fetch(..., { redirect: 'follow' }),实现上并未限制重定向次数(取决于底层 fetch 默认策略)。建议要么在实现里显式限制 redirect 次数,要么把文档改成与实现一致,避免误导用户。

Suggested change
- 请求超时 15 秒,最多跟随 3 次重定向
- 请求超时 15 秒,重定向策略以当前运行时实现为准

Copilot uses AI. Check for mistakes.
Comment thread docs/plugins-usage.md
Comment on lines +84 to +91
```
{userData}/
├── plugins/
│ ├── scripts/{id}.js 脚本源码
│ ├── manifest.json 已安装列表
│ ├── data/{id}.json 插件私有 KV 存储(splayer.storage 写入的)
│ └── logs/{id}.log 插件日志(splayer.log)
```
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文档列出了 {userData}/plugins/logs/{id}.log 并声称卸载会清理日志,但当前实现里没有看到对 plugins/logs 的写入/清理逻辑(registry.uninstall 只删 scripts 与 data)。建议补齐日志落盘与清理实现,或先从文档中移除 logs 目录描述,避免与实际行为不一致。

Copilot uses AI. Check for mistakes.
Comment on lines +254 to +257
case "fatal":
this.events.onFatal(msg.error);
done(Object.assign(new Error(msg.error.message), { code: msg.error.code }));
return;
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

收到 worker 的 fatal 消息后这里只 done(reject),但没有 kill() 子进程;而 sandbox.worker.ts 在发送 fatal 后也不会自动退出,容易留下“已 fatal 但仍存活”的 utilityProcess,造成资源泄漏/心跳逻辑异常。建议在 case 'fatal' 里主动 this.kill()(并确保只执行一次),保证子进程被回收。

Copilot uses AI. Check for mistakes.
Comment on lines +327 to +333
const ctrl = new AbortController();
inflight.set(msg.requestId, ctrl);
try {
const data = await handler(msg.params);
inflight.delete(msg.requestId);
if (ctrl.signal.aborted) {
send({
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel 分支里调用了 AbortController.abort(),但在 call 分支中创建的 ctrl 并没有传递给 handler(也没有注入到 sandbox 全局让脚本可感知),所以取消/超时只能在 handler 结束后“丢弃结果”,无法真正中断耗时任务。建议把 signal 通过第二参数传给 handler(或在 params 中附带)并在 lx shim / splayer.on 的类型上约定可选的 signal。

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +49
let body: BodyInit | undefined;
if (opts.body != null) {
if (typeof opts.body === "string") body = opts.body;
else if (opts.body instanceof ArrayBuffer) body = opts.body;
else body = (opts.body as Uint8Array).buffer as ArrayBuffer;
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

构造请求 body 时把 Uint8Array 转成了 opts.body.buffer。如果插件传入的是 subarray()(存在 byteOffset/byteLength),直接取 .buffer 会把整块底层 ArrayBuffer 都发出去,导致请求体包含多余字节。建议直接把 Uint8Array 作为 BodyInit 传给 fetch,或用 opts.body.slice().buffer/Buffer.from(opts.body) 保留正确长度。

Copilot uses AI. Check for mistakes.
Comment on lines 51 to 55
/** 恢复歌词相关窗口 */
export const restoreLyricWindows = (): void => {
if (!(store.get("system.rememberWindowState") ?? true)) return;
if (store.get("windowStates.desktopLyric.visible")) createDesktopLyricWindow();
if (store.get("windowStates.dynamicIsland.visible")) createDynamicIslandWindow();
if (isWin && store.get("windowStates.taskbarLyric.visible")) {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restoreLyricWindows() 里移除了对 system.rememberWindowState 的判断,导致用户关闭“记忆窗口状态”后依然会按 windowStates.*.visible 恢复歌词窗口,和设置项语义不一致。建议恢复原来的 guard:当 rememberWindowState=false 时直接 return(或至少不要自动恢复歌词窗口)。

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +88
export const resolveUrl = async (args: PluginResolveUrlArgs): Promise<MusicUrlRes> => {
const rt = pluginRegistry.getRuntime(args.pluginId);
if (!rt) {
throw Object.assign(new Error(`plugin ${args.pluginId} not found`), {
code: PluginErrorCodes.NOT_FOUND,
});
}
const check = supportsAction(rt, args.source, "musicUrl");
if (!check.ok) {
throw Object.assign(
new Error(`plugin ${args.pluginId} does not support musicUrl on source ${args.source}`),
{ code: PluginErrorCodes.ACTION_UNSUPPORTED },
);
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveUrl() 在插件未 ready / 已禁用时也会走 supportsAction(),从而抛出 ACTION_UNSUPPORTED;实际更合理的错误应是 PLUGIN_NOT_READYPLUGIN_DISABLED(并且当前实现会让“加载中”状态误报为“不支持动作”)。建议在 resolveUrl 里先判断 rt.enabled/rt.status.state,分别抛对应错误码;只有 state==='ready' 时才做 sources/actions 校验。

Copilot uses AI. Check for mistakes.
Comment thread electron/main/plugins/sandbox.worker.ts Outdated
Comment on lines +257 to +265
} catch (err) {
send({
kind: "fatal",
error: {
code: "PLUGIN_SCRIPT_ERROR",
message: err instanceof Error ? `${err.message}\n${err.stack ?? ""}` : String(err),
},
});
return;
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

脚本执行失败时发送了 kind: 'fatal' 但随后只是 return,进程不会退出。主进程侧如果没及时 kill,就会残留一个空转的 sandbox 进程。建议在发送 fatal 后显式 process.exit(1)(或抛出让 uncaughtException 兜底),确保 fatal=进程终止。

Copilot uses AI. Check for mistakes.
Comment thread src/pages/Home.vue
Comment on lines +4 to +54
import { netease } from "@/apis/netease";
import type { ThemeSource } from "@/types/theme";

const theme = useThemeStore();

/** Netease 搜索测试 */
interface NeteaseSearchSong {
id: number;
name: string;
artists?: { name: string }[];
album?: { name: string };
duration?: number;
}
interface NeteaseSearchBody {
code: number;
result?: { songs?: NeteaseSearchSong[]; songCount?: number };
message?: string;
}
const searchKeyword = ref("告白气球");
const searchLoading = ref(false);
const searchError = ref("");
const searchSongs = ref<NeteaseSearchSong[]>([]);
const searchCount = ref(0);

const handleSearch = async (): Promise<void> => {
const kw = searchKeyword.value.trim();
if (!kw) return;
searchLoading.value = true;
searchError.value = "";
try {
const body = await netease.search<NeteaseSearchBody>({ keywords: kw, limit: 10 });
if (body.code !== 200) {
searchError.value = body.message ?? `code=${body.code}`;
searchSongs.value = [];
searchCount.value = 0;
return;
}
searchSongs.value = body.result?.songs ?? [];
searchCount.value = body.result?.songCount ?? 0;
} catch (err) {
searchError.value = err instanceof Error ? err.message : String(err);
searchSongs.value = [];
searchCount.value = 0;
} finally {
searchLoading.value = false;
}
};

const formatArtists = (song: NeteaseSearchSong): string =>
song.artists?.map((a) => a.name).join(" / ") ?? "";

Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里新增的“Netease 搜索测试”逻辑和 UI 属于调试/演示代码,会直接进入生产 Home 页面(包含额外的接口类型、状态、模板块),与 PR 标题“feat: Plugins”不匹配且会增加维护与发布风险。建议移除该测试区块,或至少用 import.meta.env.DEV/单独路由开关隔离到开发环境。

Copilot uses AI. Check for mistakes.
Comment thread electron/main/apis/netease/core/request.ts Fixed
Comment thread electron/main/apis/netease/core/request.ts Fixed
…ble types'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 23, 2026 05:57
…ariable'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 123 out of 127 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

electron/main/window/index.ts:56

  • 这里移除了 system.rememberWindowState 的判断,导致即使用户关闭了“记忆窗口状态”,启动时仍会按 windowStates.*.visible 自动恢复歌词窗口,属于配置失效的行为回归。建议恢复原有判断:当 rememberWindowState=false 时直接 return,或同步清空 windowStates.visible 以避免下次误恢复。

Comment on lines +85 to +104
init(): void {
ensureDirs();
const stored = readStored();
const enabledMap = store.get("plugins.enabled") as Record<string, boolean>;

// 首先加载 stored manifest
for (const [id, manifest] of Object.entries(stored.plugins)) {
const scriptPath = path.join(scriptsDir(), manifest.fileName);
let source = "";
try {
source = fs.readFileSync(scriptPath, "utf-8");
// 重新解压(防止脚本外部被替换为 gz_)
const { source: s } = loadScript(source, false, manifest.fileName);
source = s;
} catch (err) {
coreLog.warn(`[plugin] failed to read ${manifest.fileName}:`, err);
continue;
}
const enabled = enabledMap[id] ?? true;
this.runtimes.set(id, {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init() 里直接把 store.get("plugins.enabled") 断言成 Record<string, boolean>,如果老用户配置里还没有 plugins 字段,这里可能为 undefined,随后访问 enabledMap[id] 会在启动时直接抛错。建议对 get 结果做兜底:const enabledMap = (store.get("plugins.enabled") as Record<string, boolean> | undefined) ?? {},或在 store 初始化/迁移时补齐默认 plugins 配置。

Copilot uses AI. Check for mistakes.
Comment on lines +306 to +323
onExit: (isCrash) => {
if (!isCrash) return;
rt.restartAttempts++;
if (rt.restartAttempts > RESTART_MAX_ATTEMPTS) {
this.setStatus(rt, {
state: "error",
error: {
code: PluginErrorCodes.WORKER_CRASHED,
message: "plugin crashed too many times",
},
});
return;
}
const delayMs = [2_000, 8_000, 30_000][rt.restartAttempts - 1] ?? 30_000;
setTimeout(() => {
if (rt.enabled) this.start(rt).catch(() => {});
}, delayMs);
},
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

崩溃自动重启的 setTimeout 仅判断 rt.enabled,但 uninstall() 并不会先把 rt.enabled 置为 false;如果插件崩溃→已排队重启→用户在延迟窗口内卸载,定时器仍可能触发并把已卸载插件重新 start(rt 仍被闭包引用)。建议在回调里额外判断 runtime 是否仍在 registry 中(例如 this.runtimes.has(rt.manifest.id)),或在 uninstall() 里先 rt.enabled=false 并记录/清理该 rt 的重启定时器。

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +7
/**
* 插件系统 IPC
*
* 渲染端通过 `window.api.plugins.*` 调用以下 channel:
* - plugin:list / install / pickAndInstall / installFromUrl / uninstall / setEnabled
* - plugin:search / resolveUrl
* 并订阅 `plugin:status` 广播以更新 UI。
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文件头注释声明支持 plugin:search channel,但当前实际只注册了 list/install/pickAndInstall/installFromUrl/uninstall/setEnabled/resolveUrl(没有 plugin:search)。建议删除注释中的 plugin:search,或补齐对应 IPC handler,避免文档与实现不一致导致调用方误用。

Copilot uses AI. Check for mistakes.
Comment thread src/pages/Home.vue
Comment on lines +4 to +49
import { netease } from "@/apis/netease";
import type { ThemeSource } from "@/types/theme";

const theme = useThemeStore();

/** Netease 搜索测试 */
interface NeteaseSearchSong {
id: number;
name: string;
artists?: { name: string }[];
album?: { name: string };
duration?: number;
}
interface NeteaseSearchBody {
code: number;
result?: { songs?: NeteaseSearchSong[]; songCount?: number };
message?: string;
}
const searchKeyword = ref("告白气球");
const searchLoading = ref(false);
const searchError = ref("");
const searchSongs = ref<NeteaseSearchSong[]>([]);
const searchCount = ref(0);

const handleSearch = async (): Promise<void> => {
const kw = searchKeyword.value.trim();
if (!kw) return;
searchLoading.value = true;
searchError.value = "";
try {
const body = await netease.search<NeteaseSearchBody>({ keywords: kw, limit: 10 });
if (body.code !== 200) {
searchError.value = body.message ?? `code=${body.code}`;
searchSongs.value = [];
searchCount.value = 0;
return;
}
searchSongs.value = body.result?.songs ?? [];
searchCount.value = body.result?.songCount ?? 0;
} catch (err) {
searchError.value = err instanceof Error ? err.message : String(err);
searchSongs.value = [];
searchCount.value = 0;
} finally {
searchLoading.value = false;
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里新增了“Netease 搜索测试”整块 UI 与请求逻辑,看起来是调试/验收用代码,但会随正式构建一起发布并暴露内部接口调用能力,且会让 Home 页承担额外网络请求与 UI 噪音。建议在合入前移除该测试区,或至少用开发环境开关(如 import.meta.env.DEV)/隐藏入口包起来,避免影响生产用户。

Copilot uses AI. Check for mistakes.
Comment thread electron/preload/index.ts
Comment on lines +244 to +250
apis: {
// 调用任意平台的任意接口
call: (platform: string, name: string, params?: Record<string, unknown>) =>
ipcRenderer.invoke("apis:call", platform, name, params ?? {}),
// 清空指定平台的登录态
clearSession: (platform: string) => ipcRenderer.invoke("apis:clearSession", platform),
},
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preload 侧 apis.call/clearSessionplatform 参数标成 string,但共享类型里 ApisApi 的平台是 ApiPlatform("netease" | "qqmusic" | "kugou")。这会弱化类型约束,也让渲染端更容易传入非法平台字符串。建议把这里的签名改为使用 ApiPlatform,并让返回值类型显式对齐 ApiCallResponse

Copilot uses AI. Check for mistakes.
@imsyy imsyy merged commit 8389c74 into dev Apr 23, 2026
9 checks passed
@imsyy imsyy deleted the plugins branch April 24, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants