Files
music-hub/CLAUDE.md
T
Jocay 53d523d7a0 feat: 粘贴网易云 / QQ 音乐歌单分享链接导入为自建歌单
- POST /api/playlists/import:resolveLink 识别歌单 / 专辑链接,拉取全部曲目后建歌单,简介注明来源与原简介
- 歌单页新增「导入歌单」;上游只返回部分曲目时提示实际获取数
- 网易云歌单详情补上 description

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:45:38 +08:00

49 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.ts`
- `core/providers/{netease,qq}/` 实现 `MusicProvider`(`core/types.ts`),在 `providers/index.ts` 注册;`resolveLink` 处理分享短链
- QQ:`qq/client.ts` 的 `musicu()` 在 comm 中带 `uin` + `authst` 并附 Cookie —— 缺这一步拿不到 VIP 音源
- `queue.ts` — 任务存 SQLite `tasks` 表,进度只在内存,经 `events.ts` 的 bus 推送;`routes/api.ts` 的 `/api/events` 是 SSE
- `playlists.ts` — 自建歌单(`playlists` / `playlist_items` 表);条目要么是在线曲目(source + track_id)要么是曲库文件(path),`item_key` 去重。读取时给在线曲目补 `localFile`、给文件补 `missing`。`importPlaylist` 经 `resolveLink` 把网易云 / QQ 歌单或专辑链接导入为新歌单(`POST /api/playlists/import`)
- `accounts.ts`(Cookie 用 `secret.ts` AES-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 对 `null` body 返回 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,必须配合文字标签使用