147 lines
4.8 KiB
Markdown
147 lines
4.8 KiB
Markdown
# Koneko QZ Music v2/v3 插件开发避坑指南
|
||
|
||
> 版本: 0.0.2 | 作者: 云汀(Miao-moe) | 目标: 支持迁移到其他 AI 继续开发
|
||
|
||
---
|
||
|
||
## 一、项目背景
|
||
|
||
QZ Music v2/v3 是一款 Android/PC 音乐播放器,支持通过**拓展插件**接入多平台音源。插件系统基于 Node.js 运行时(Javet/V8),每个插件是一个单独的 `.js` 文件,通过 `module.exports` 导出接口。
|
||
|
||
### 插件加载机制
|
||
|
||
- 运行时环境变量通过 `global.env` 访问,**不是** `process.env`
|
||
- 插件为单 `.js` 文件格式
|
||
|
||
## 二、Javet/V8 兼容性大坑(最重要)
|
||
|
||
QZ Music 使用 Javet 作为 JS 运行时(基于 V8),**不支持现代 ES 语法**:
|
||
|
||
| 语法 | 状态 | 正确写法 |
|
||
|------|------|---------|
|
||
| `let` / `const` | ❌ | `var` |
|
||
| 箭头函数 `() => {}` | ❌ | `function() {}` |
|
||
| `async` / `await` | ❌ | `Promise` 链式 |
|
||
| `catch { }`(无参数)| ❌ | `catch (e) { }` |
|
||
| `Promise.allSettled` | ❌ | `Promise.all` + 手动包装 |
|
||
| `Object.entries` / `Object.values` | ❌ | `for...in` 遍历 |
|
||
| `Array.prototype.includes` | ❌ | `indexOf(...) !== -1` |
|
||
| `String.prototype.startsWith` | ❌ | `indexOf(...) === 0` |
|
||
| `class` | ❌ | 对象字面量 |
|
||
| 模板字符串 `${}` | ✅ | 可用 |
|
||
| `Buffer` | ✅ | Node.js 内置 |
|
||
|
||
### Promise.allSettled 替代方案
|
||
|
||
```js
|
||
Promise.all(promises.map(function(p) {
|
||
return p.then(function(v) {
|
||
return { status: 'fulfilled', value: v }
|
||
}).catch(function(e) {
|
||
return { status: 'rejected', reason: e }
|
||
})
|
||
})).then(function(results) {
|
||
for (var i = 0; i < results.length; i++) {
|
||
if (results[i].status === 'fulfilled') return results[i].value
|
||
}
|
||
throw new Error('all failed')
|
||
})
|
||
```
|
||
|
||
## 三、搜索结果格式
|
||
|
||
App 的 `MusicListResponse` 要求**必须有 `list` 字段**,不是 `songs`!
|
||
|
||
```js
|
||
// ✅ 正确
|
||
return { list: [...], allPage: N, limit: N, total: N, source: 'tx' }
|
||
|
||
// ❌ 错误
|
||
return { songs: [...], total: N }
|
||
```
|
||
|
||
### 字段名对照
|
||
|
||
| 含义 | 正确字段 | 错误字段 |
|
||
|------|---------|---------|
|
||
| 歌手 | `artists` | `artist` |
|
||
| 封面图 | `pic`/`mPic`/`sPic` | `picUrl` |
|
||
| 时长 | `interval` (m:ss) | `duration` |
|
||
| 列表 | `list` | `songs` |
|
||
|
||
## 四、各平台踩坑点
|
||
|
||
### QQ音乐 (tx)
|
||
- 搜索签名:`zzcSign` = SHA1 + 索引提取 + XOR + base64
|
||
- 封面图:有专辑ID用 T002,无专辑ID用 T001
|
||
- getUrl 音质参数:带 `k` (128k/320k/999k)
|
||
|
||
### 酷狗音乐 (kg)
|
||
- 搜索返回值字段是 `errcode`(不是 `error_code`)
|
||
- 封面图:替换 `{size}` 为 `400`
|
||
|
||
### 酷我音乐 (kw)
|
||
- 音质信息在 `N_MINFO` 字段解析
|
||
- 封面图:`https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg`
|
||
|
||
### 网易云音乐 (wy)
|
||
- 搜索用 GET:`/api/search/get/web`,不需要 weapi
|
||
- 封面图:`picId` 需 Base64 编码
|
||
- getUrl 音质参数:数字格式 `128000`/`320000`/`999000`
|
||
- Cookie 用于 weapi 加密接口(每日推荐、私人FM、喜欢歌曲、歌单、专辑、歌词)
|
||
|
||
### 咪咕音乐 (mg)
|
||
- 搜索需 MD5 签名
|
||
- 封面图:相对路径需拼接 `https://d.musicapp.migu.cn`
|
||
|
||
### GIT音源 (git)
|
||
- 无搜索,纯音源
|
||
- getUrl 音质参数:`128k`/`320k`/`999k`
|
||
|
||
## 五、getUrl 容灾逻辑
|
||
|
||
所有平台使用**并发测速**模式:同时请求多个 API,取第一个成功结果。
|
||
|
||
API 调用顺序:
|
||
1. 聆澜(需 `ceru_key` 环境变量)
|
||
2. HUIBQ (lxmusicapi)
|
||
3. 星海 / 忆音 / 念心 / 长青 / 星海备 / fish / HYW
|
||
|
||
网易云额外有:
|
||
4. bb / lx / ymc / unms / 官方 weapi
|
||
|
||
## 六、环境变量说明
|
||
|
||
| key | 用途 | 适用平台 | 必填 |
|
||
|-----|------|---------|------|
|
||
| `ceru_key` | 聆澜音源 API 密钥 | 全部 | 否 |
|
||
| `playlist_url` | 网易云个人主页链接 | 网易云 | 否 |
|
||
| `cookie` | 网易云 Cookie | 网易云 | 否 |
|
||
|
||
## 七、版本管理
|
||
|
||
- 所有平台统一版本号
|
||
- 当前版本:**0.0.2**
|
||
- 文件名格式:`Koneko_{平台名}_v{版本号}.js`
|
||
|
||
## 八、常见报错
|
||
|
||
| 报错 | 原因 | 解决 |
|
||
|------|------|------|
|
||
| `Cannot find module 'axios'` | 用了 axios | 改用 `http`/`https` 内置模块 |
|
||
| `Field 'list' is required` | 搜索返回 `songs` 而非 `list` | 改字段名为 `list` |
|
||
| `SyntaxError: Invalid or unexpected token` | 用了 `catch { }` | 改为 `catch (e) { }` |
|
||
| 搜索无结果 | 字段名不匹配 | 检查 `errcode` vs `error_code` |
|
||
| 播放失败 | `mapBr` 返回格式不对 | QQ/kg/kw/mg/git 用 `320k`,wy 用 `320000` |
|
||
| 封面图不显示 | URL 格式错误或跨域 | 检查各平台封面图拼接规则 |
|
||
|
||
## 九、后续开发建议
|
||
|
||
1. 每次修改全部平台统一升级版本号
|
||
2. 先在浏览器/curl 测试 API 是否可用
|
||
3. 注意 Javet 兼容性,避免现代 JS 语法
|
||
4. 搜索返回务必包含 `list` 字段
|
||
5. getUrl 注意各平台音质参数格式差异
|
||
6. 所有异步操作加 `.catch()` 兜底
|
||
7. 优先使用官方搜索接口,音源用第三方 API 容灾
|