# MusicFree Pure Music_Server Plugin Design 日期:2026-04-19 状态:已确认设计,待实现计划 范围:`Music_Server` 榜单详情接口补齐、`MusicFree` 纯 `Music_Server` 插件、旧插件兼容壳 ## 1. 背景 当前 `Music_Server` 已经提供这些面向 `MusicFree` 的能力: - `GET /mf/v1/recommend/tags` - `GET /mf/v1/recommend/sheets` - `GET /mf/v1/playlists/{id}` - `GET /mf/v1/playlists/{id}/tracks` - `GET /mf/v1/toplists` - `GET /mf/v1/search/songs` - `POST /mf/v1/media/resolve` 同时,当前 `MusicFree` 使用的 [netease_17000.js](/d:/source/MusicFree/keep-alive-master/Music_Free/netease_17000.js) 仍然是旧的网易 relay 风格插件,内部直接调用旧服务端接口,并带有与本轮目标无关的能力: - `album` 搜索与详情 - `artist` 搜索与作品 - `lyric` - `importMusicSheet` - 旧的网易与 relay 回退逻辑 本轮要把插件收敛成“纯 `Music_Server` 插件”,并把服务端缺失的榜单详情链路补齐,使 `MusicFree` 可以完整走“搜歌、看歌单、看榜单、播放”这一条自有服务链路。 ## 2. 目标 本轮目标: - `Music_Server` 补齐榜单详情与榜单歌曲分页接口 - `MusicFree` 新增正式插件 `music_server.js` - 旧插件 `netease_17000.js` 保留为兼容壳,转发到新插件 - 插件只依赖 `Music_Server`,不再直连旧网易 relay 或其他平台 - 插件能力只保留当前服务端已支持的: - `music` 搜索 - 推荐歌单 - 歌单详情 - 榜单列表与榜单详情 - 播放解析 非目标: - 不做 `album` 搜索或专辑详情 - 不做 `artist` 搜索或歌手作品页 - 不做 `lyric` - 不做 `importMusicSheet` - 不做插件侧多平台回退 - 不做插件直连网易、QQ、酷我等外部平台 ## 3. 总体设计 采用“两端各补一小段”的方式完成: 1. `Music_Server` - 保持现有 `/mf/v1/*` 结构不变 - 新增榜单详情与榜单歌曲接口 - 返回结构与歌单详情链路保持一致 2. `MusicFree` 插件 - 新增正式插件文件 `music_server.js` - 旧文件 `netease_17000.js` 退化为兼容壳 - 所有方法仅调用 `Music_Server` - 仅负责请求与字段映射,不承担内容回源逻辑 这样做的原则是: - 服务端负责内容真相与业务契约 - 插件只负责协议适配 - 旧入口兼容,新入口语义清晰 ## 4. Music_Server 设计 ### 4.1 数据模型现状 `catalog_read.db` 现有导出脚本 [export_catalog_read.py](/d:/source/musicdl-catalog-sync-worktrees/Music_Server/scripts/export_catalog_read.py) 已经包含: - `catalog_toplists` - `catalog_toplist_tracks` 因此本轮不需要改读模型导出结构,只需要把现有数据通过读取层和路由层暴露出来。 ### 4.2 读取层补齐 在 [catalog_reader.py](/d:/source/musicdl-catalog-sync-worktrees/Music_Server/src/music_server/services/catalog_reader.py) 中新增两个能力: - `get_toplist(toplist_id: str) -> ToplistRow | None` - `list_toplist_tracks(toplist_id: str, page: int, page_size: int) -> list[PlaylistTrackRow]` 约束: - `get_toplist()` 从 `catalog_toplists` 取单条 - `list_toplist_tracks()` 从 `catalog_toplist_tracks` 联结 `catalog_tracks` - 排序按 `position asc` - 返回的歌曲对象字段与 `list_playlist_tracks()` 保持一致: - `song_id` - `name` - `singers` - `album` - `cover_url` - `duration_ms` ### 4.3 路由层补齐 在 [mf_catalog.py](/d:/source/musicdl-catalog-sync-worktrees/Music_Server/src/music_server/routes/mf_catalog.py) 中新增: - `GET /mf/v1/toplists/{toplist_id}` - `GET /mf/v1/toplists/{toplist_id}/tracks?page=&page_size=` 返回约束: - `/mf/v1/toplists/{toplist_id}` - 返回对象 shape 与 `/mf/v1/playlists/{playlist_id}` 一致 - `id` 使用 `catalogsync:toplist:{toplist_id}` - `platform` 固定返回 `catalogsync` - `/mf/v1/toplists/{toplist_id}/tracks` - 返回对象 shape 与 `/mf/v1/playlists/{playlist_id}/tracks` 一致 - 返回: - `isEnd` - `musicList` 错误语义: - 找不到榜单:`404` - 缺少或错误 Bearer Token:`401` - 空榜单:`200` 且 `musicList: []` ### 4.4 与现有接口的关系 本轮不改这些接口的既有行为: - `GET /mf/v1/recommend/tags` - `GET /mf/v1/recommend/sheets` - `GET /mf/v1/playlists/{id}` - `GET /mf/v1/playlists/{id}/tracks` - `GET /mf/v1/toplists` - `GET /mf/v1/search/songs` - `POST /mf/v1/media/resolve` 也不改导出脚本中的歌单、歌曲、文件位置逻辑。 ## 5. MusicFree 插件设计 ### 5.1 文件布局 插件文件放在 `D:\source\MusicFree\keep-alive-master\Music_Free`。 本轮交付: - 新增 [music_server.js](/d:/source/MusicFree/keep-alive-master/Music_Free/music_server.js) - 改造 [netease_17000.js](/d:/source/MusicFree/keep-alive-master/Music_Free/netease_17000.js) 处理方式: - `music_server.js` 作为正式插件文件 - `netease_17000.js` 只保留一层兼容壳: - `module.exports = require("./music_server")` 这样既保留旧入口,又让正式插件名称与实际能力一致。 ### 5.2 插件元数据 建议插件元数据固定为: - `platform: "Music_Server"` - `version`: 本轮实现版本 - `author`: 保留现有项目惯例 - `supportedSearchType: ["music"]` `userVariables` 只保留: - `baseUrl` - `accessToken` 规则: - `baseUrl` 指向 `Music_Server` 部署地址 - `accessToken` 指向 `Music_Server` Bearer Token - 插件不内置 NAS 地址、旧 relay 地址或其他平台地址 ### 5.3 请求层规则 插件请求层保持极薄: - 所有 JSON 业务请求带: - `Authorization: Bearer ` - `baseUrl` 统一去掉尾部斜杠 - 不在插件内保存本地数据库或缓存目录 - 不做多平台搜索与回退 `media/resolve` 特殊规则: - 若 `stream.url` 是相对路径,例如 `/mf/v1/media/stream/...` - 插件自动拼接为 `${baseUrl}${url}` - 若 `stream.url` 已是绝对地址 - 直接原样使用 ### 5.4 插件能力映射 插件仅实现并暴露这些方法: - `search(query, page, "music")` - 对应 `GET /mf/v1/search/songs` - `getRecommendSheetTags()` - 对应 `GET /mf/v1/recommend/tags` - `getRecommendSheetsByTag(tag, page)` - 对应 `GET /mf/v1/recommend/sheets` - `getMusicSheetInfo(sheetItem, page)` - 对应: - `GET /mf/v1/playlists/{id}` - `GET /mf/v1/playlists/{id}/tracks` - `getTopLists()` - 对应 `GET /mf/v1/toplists` - `getTopListDetail(topListItem, page)` - 对应: - `GET /mf/v1/toplists/{id}` - `GET /mf/v1/toplists/{id}/tracks` - `getMediaSource(musicItem, quality)` - 对应 `POST /mf/v1/media/resolve` 不再实现: - `album` - `artist` - `lyric` - `importMusicSheet` ### 5.5 稳定 ID 规则 插件与服务端之间继续使用稳定 public id: - 歌单:`catalogsync:playlist:{playlist_id}` - 榜单:`catalogsync:toplist:{toplist_id}` - 歌曲:`catalogsync:song:{song_id}` 插件只负责从 public id 解析出最后一段真实 id,然后发给服务端路由。 ## 6. 字段映射 ### 6.1 歌单与榜单对象 服务端返回的歌单、榜单对象直接映射成 MusicFree 的 `sheetItem` / `topListItem` 风格对象,核心字段统一为: - `id` - `platform` - `title` - `coverImg` - `description` - `worksNum` - `playCount` ### 6.2 歌曲对象 服务端返回的 `musicItem` 直接映射,核心字段: - `id` - `platform` - `title` - `artist` - `album` - `artwork` - `duration` ### 6.3 播放源对象 `getMediaSource()` 返回: - `url` - `headers` - `quality` 其中: - `headers` 来自服务端 `stream.headers` - `quality` 优先取服务端 `selected_source.quality` ## 7. 错误处理 ### 7.1 插件配置错误 若 `baseUrl` 或 `accessToken` 为空: - 插件直接抛出明确错误 - 不做隐式默认值兜底 - 不静默退回旧服务 ### 7.2 浏览型接口错误 对于这些接口: - 搜歌 - 推荐歌单 - 榜单列表 如果请求失败: - 插件返回空结果 - 同时保留清晰错误信息,方便调试 ### 7.3 详情接口错误 对于: - `getMusicSheetInfo` - `getTopListDetail` 若详情页或 tracks 请求失败: - 不伪造旧平台数据 - 返回空 `musicList` - 第 1 页如果头信息获取失败,不补假数据 ### 7.4 播放解析错误 若 `/mf/v1/media/resolve` 失败或无可播放地址: - `getMediaSource()` 返回 `null` - 不回退到旧网易、QQ、酷我逻辑 ## 8. 测试设计 ### 8.1 Music_Server 测试 在 `Music_Server` 仓库补这些测试: - `CatalogReader` - `get_toplist()` - `list_toplist_tracks()` - `mf_catalog` 路由 - `GET /mf/v1/toplists/{id}` - `GET /mf/v1/toplists/{id}/tracks` 重点覆盖: - 正常返回 shape - 第 1 页与末页 `isEnd` - `404` - `401` ### 8.2 MusicFree 插件测试 在 `MusicFree` 仓库新增独立测试,测试目标是 `music_server.js`,不再复用旧 `netease_17000.test.js` 的网易 fallback 语义。 重点覆盖: - `userVariables` 只有 `baseUrl` 与 `accessToken` - `supportedSearchType` 只有 `music` - 搜歌走 `/mf/v1/search/songs` - 歌单详情走 `/mf/v1/playlists/*` - 榜单详情走 `/mf/v1/toplists/*` - `getMediaSource()` 正确处理相对流地址与绝对流地址 - `netease_17000.js` 兼容壳导出与 `music_server.js` 相同插件对象 ## 9. 验收标准 联调通过标准: - `MusicFree` 能搜歌 - 能看推荐歌单 - 能点进歌单并加载歌曲列表 - 能看榜单列表 - 能点进榜单并加载歌曲列表 - 能播放 `Music_Server` 返回的流地址 - 插件逻辑不再依赖旧网易 relay 搜索或播放路径 ## 10. 实施边界 代码边界保持如下: - `Music_Server` 服务端代码只改 `D:\source\musicdl-catalog-sync-worktrees\Music_Server` - `MusicFree` 插件代码只改 `D:\source\MusicFree\keep-alive-master\Music_Free` - 不回到 `D:\source\musicdl` 老仓库实现服务逻辑 本 spec 只覆盖“纯 `Music_Server` 插件 + 服务端榜单详情补齐”这一小段工作,后续若要继续扩充 `album`、`artist`、`lyric`,需要新 spec 或新实现计划单独推进。