Files
music-hub/CLAUDE.md
T
Jocay c1ad24aace feat: 全屏「正在播放」页与跨平台兜底播放
- NowPlaying:参考 Apple Music tvOS —— 模糊封面铺底、大号逐行歌词(按距离渐隐 / 模糊、逐行跟随)、接下来列表、可拖动进度条,静置后隐藏控件;Esc 收起、←/→ 快退 / 快进
- 播放兜底链:曲库文件 → 原平台 → 另一个平台的同名歌曲,出错时依次切换并保留进度,合并为一条提示
- 新增 /api/:source/stream-alt:按歌名、歌手、时长在另一个平台匹配同一首歌并代理播放

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

49 lines
5.1 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`;`/:source/stream-alt` 按歌名 + 歌手 + 时长到另一个平台找同一首歌)
- `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。在线曲目带兜底链 `alt`:曲库文件 → 原平台 → 另一平台同名歌曲,`<audio>` 出错时依次切换并保留进度,`player.via` 是实际出声的音源。`components/NowPlaying.vue` 是全屏正在播放(`player.full`)。`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,必须配合文字标签使用