53d523d7a0
- POST /api/playlists/import:resolveLink 识别歌单 / 专辑链接,拉取全部曲目后建歌单,简介注明来源与原简介 - 歌单页新增「导入歌单」;上游只返回部分曲目时提示实际获取数 - 网易云歌单详情补上 description Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.8 KiB
4.8 KiB
CLAUDE.md
声匣:自托管音乐下载 Web 服务(网易云 + QQ 音乐),本机 npm start 或 NAS Docker 运行。UI、注释、日志均为中文。
命令
npm run dev— 后端node --watch server/src/main.ts(:8066)+ Vite(:5173,代理/api)npm start— 生产模式(需先npm run build)npm run typecheck—tsc(server)+vue-tsc(web)。改动后必须通过npm run smoke -- <id|链接> [quality] [album|flat]— 端到端跑一次下载流水线(会导入 QQ Cookie)
没有测试框架;用 smoke、curl /api/*,以及无头浏览器截图验证。测试下载请用 MUSIC_DIR / DATA_DIR 指向临时目录,不要污染 ./music 与 ./data。
架构
单个 package.json:
server/src/— Koa 3,Node 原生 TypeScript 类型剥离直接运行,无构建步骤- 只能用可擦除语法(
erasableSyntaxOnly):不能用 enum / namespace / 参数属性 - 相对导入必须写
.ts;类型导入用import type(verbatimModuleSyntax) core/与平台无关:pipeline.ts(下载流水线)、tagging.ts、lyrics.ts、layout.ts、quality.ts、net/http.tscore/providers/{netease,qq}/实现MusicProvider(core/types.ts),在providers/index.ts注册;resolveLink处理分享短链- QQ:
qq/client.ts的musicu()在 comm 中带uin+authst并附 Cookie —— 缺这一步拿不到 VIP 音源 queue.ts— 任务存 SQLitetasks表,进度只在内存,经events.ts的 bus 推送;routes/api.ts的/api/events是 SSEplaylists.ts— 自建歌单(playlists/playlist_items表);条目要么是在线曲目(source + track_id)要么是曲库文件(path),item_key去重。读取时给在线曲目补localFile、给文件补missing。importPlaylist经resolveLink把网易云 / QQ 歌单或专辑链接导入为新歌单(POST /api/playlists/import)accounts.ts(Cookie 用secret.tsAES-GCM 加密存储)、settings.ts(运行时设置)、auth.ts(可选访问密码)、library.ts(曲库工具)、stream.ts(在线播放代理,音质取设置playQuality)db.ts—node:sqlite;history / tasks / settings / playlists / playlist_items 表
- 只能用可擦除语法(
web/— Vite 8 + Vue 3.5 + Vue Router 5,无 UI 库lib/store.ts全局状态 + SSE;lib/api.ts类型与请求lib/player.ts播放器:单个<audio>+ 队列(shallowRef,整体替换)。曲目统一转成PlayItem(fromTrack/fromLocal/fromPlaylistItem),key 为source:id或file:path;队列与进度存 localStorage。lib/playlists.ts是歌单缓存与全局「加入歌单」弹窗(pickPlaylist)components/TrackList.vue是所有曲目列表(搜索 / 专辑 / 歌单 / 歌手)的核心- 样式只用
styles/tokens.css的 CSS 变量;base.css有.page.btn.seg.badge等通用类
约定
- 统一音质键
standard|high|lossless|hires|master|spatial|effect;provider 映射到平台参数,不可用时按fallbackChain降级。QQ 只声明到 hires - 目录:
album布局 ={第一位专辑艺人}/{专辑}/{NN}. {歌名}.ext(与 NAS 曲库一致);flat={歌手} - {歌名}.ext - 下载先写
.part再改名;已存在判定:≥ 音源 99% 且 ≤ 音源 + 8MB(内嵌封面会变大) FatalError表示重试无意义;队列间隔gapMs(默认 400ms)防止打爆上游- 服务端永远不把 Cookie 原文返回前端;
relFile等路径一律用/ - 传给 TrackList 的数组必须稳定(用 computed),否则每次渲染都会清空选择
- 前端
ref<null>的接口要返回对象包一层(Koa 对nullbody 返回 204) - 子组件根元素会继承父组件的 scoped 样式作用域:PlayerBar 根元素不能用 App.vue 里已有的类名(如
.dock) - 列表行用
content-visibility: auto+contain-intrinsic-size,上千首也只渲染视口附近的行 - QQ 登录在
qq/login.ts:扫码 = ptlogin2 → graph.qq.com OAuth →QQConnectLogin.QQLogin;续期 =music.login.LoginServer.Login(网页 Cookie 没有 refresh_key 也能续期,旧 key 不会失效)。accounts.ts的 keeper 每 3 小时检查,过半寿命(key 有效 3 天)即续期。有效性必须用GetLoginUserInfo判断 ——vip_login_base对失效 key 仍返回 code 0 - NAS 部署:宿主机文件是 0700,镜像里必须
chmod -R a+rX /app;境外请求靠NODE_USE_ENV_PROXY+HTTP(S)_PROXY,NO_PROXY覆盖 QQ / 网易云域名 - TypeScript 固定 6.x:TS 7 没有 JS API,vue-tsc 无法使用
- 网易云红 / QQ 绿已用 dataviz 校验器验证(深色 QQ 绿
#42a07d);二者 CVD 区分度在 6–8,必须配合文字标签使用