From 42ff3bde8e66327cfdba6507ec22d065827e0d96 Mon Sep 17 00:00:00 2001 From: miao-moe Date: Sat, 20 Jun 2026 12:48:28 +0800 Subject: [PATCH] chore: reset version to v0.0.1 --- ...4039d1dfbbf7_Koneko_插件开发文档_v0.0.3.md | 491 +++++++------- ...9d1dfbbf7_Koneko插件开发避坑指南_v0.0.3.md | 315 ++++----- ...音源_v0.0.4.js => Koneko_GIT音源_v0.0.1.js | 32 +- ...Q音乐_v0.0.4.js => Koneko_QQ音乐_v0.0.1.js | 50 +- ...音乐_v0.0.4.js => Koneko_咪咕音乐_v0.0.1.js | 44 +- Koneko_插件开发文档_v0.0.1.md | 221 +++++++ Koneko_插件开发文档_v0.0.4.md | 239 ------- ...音乐_v0.0.4.js => Koneko_网易云音乐_v0.0.1.js | 51 +- ...音乐_v0.0.4.js => Koneko_酷我音乐_v0.0.1.js | 50 +- ...音乐_v0.0.4.js => Koneko_酷狗音乐_v0.0.1.js | 42 +- Koneko插件开发避坑指南_v0.0.1.md | 123 ++++ Koneko插件开发避坑指南_v0.0.4.md | 146 ----- ...件规范│QZ-Music-Plugin-Development-Guide.md | 607 ++++++++--------- QZ_Music-V2 插件规范(v1.0.3).md | 620 ++++++++---------- ...初版)│QZ_Music-V2-Plugin-Development-Guide.md | 619 ++++++++--------- README.md | 54 +- 16 files changed, 1687 insertions(+), 2017 deletions(-) rename Koneko_GIT音源_v0.0.4.js => Koneko_GIT音源_v0.0.1.js (78%) rename Koneko_QQ音乐_v0.0.4.js => Koneko_QQ音乐_v0.0.1.js (91%) rename Koneko_咪咕音乐_v0.0.4.js => Koneko_咪咕音乐_v0.0.1.js (88%) create mode 100644 Koneko_插件开发文档_v0.0.1.md delete mode 100644 Koneko_插件开发文档_v0.0.4.md rename Koneko_网易云音乐_v0.0.4.js => Koneko_网易云音乐_v0.0.1.js (89%) rename Koneko_酷我音乐_v0.0.4.js => Koneko_酷我音乐_v0.0.1.js (89%) rename Koneko_酷狗音乐_v0.0.4.js => Koneko_酷狗音乐_v0.0.1.js (87%) create mode 100644 Koneko插件开发避坑指南_v0.0.1.md delete mode 100644 Koneko插件开发避坑指南_v0.0.4.md diff --git a/6a2d8996cc974039d1dfbbf7_Koneko_插件开发文档_v0.0.3.md b/6a2d8996cc974039d1dfbbf7_Koneko_插件开发文档_v0.0.3.md index 1497471..31e34a9 100644 --- a/6a2d8996cc974039d1dfbbf7_Koneko_插件开发文档_v0.0.3.md +++ b/6a2d8996cc974039d1dfbbf7_Koneko_插件开发文档_v0.0.3.md @@ -1,55 +1,38 @@ -# Koneko 插件开发踩坑与最终代码文档 - -> 版本:0.0.3 -> 作者:云汀(Miao-moe) -> 整理日期:2026-06-19 -> 用途:汇总与 QZ Music v2 拓展插件开发相关的全部踩坑点、最终代码、链接/API,便于迁移到其他 AI/工具继续迭代。 - +# Koneko 鎻掍欢寮€鍙戣俯鍧戜笌鏈€缁堜唬鐮佹枃妗? +> 鐗堟湰锛?.0.3 +> 浣滆€咃細浜戞眬(Miao-moe) +> 鏁寸悊鏃ユ湡锛?026-06-19 +> 鐢ㄩ€旓細姹囨€讳笌 QZ Music v2 鎷撳睍鎻掍欢寮€鍙戠浉鍏崇殑鍏ㄩ儴韪╁潙鐐广€佹渶缁堜唬鐮併€侀摼鎺?API锛屼究浜庤縼绉诲埌鍏朵粬 AI/宸ュ叿缁х画杩唬銆? --- -## 一、项目背景与目标 - -为 QZ Music v2 编写 6 个音乐平台拓展插件:QQ音乐、酷狗音乐、酷我音乐、网易云音乐、咪咕音乐、GIT音源。 - -核心需求: -- 官方搜索可用 -- 多 API 音源测速容灾 -- 环境变量读取 `global.env` -- 版本号从 0.0.1 开始,每次修改统一升版 -- 仅支持单 `.js` 文件格式(QZ Music v2 实际加载的是 `.js` 文件,`plugin.json` 内嵌在代码里) +## 涓€銆侀」鐩儗鏅笌鐩爣 +涓?QZ Music v2 缂栧啓 6 涓煶涔愬钩鍙版嫇灞曟彃浠讹細QQ闊充箰銆侀叿鐙楅煶涔愩€侀叿鎴戦煶涔愩€佺綉鏄撲簯闊充箰銆佸挭鍜曢煶涔愩€丟IT闊虫簮銆? +鏍稿績闇€姹傦細 +- 瀹樻柟鎼滅储鍙敤 +- 澶?API 闊虫簮娴嬮€熷鐏?- 鐜鍙橀噺璇诲彇 `global.env` +- 鐗堟湰鍙蜂粠 0.0.1 寮€濮嬶紝姣忔淇敼缁熶竴鍗囩増 +- 浠呮敮鎸佸崟 `.js` 鏂囦欢鏍煎紡锛圦Z Music v2 瀹為檯鍔犺浇鐨勬槸 `.js` 鏂囦欢锛宍plugin.json` 鍐呭祵鍦ㄤ唬鐮侀噷锛? --- -## 二、重大踩坑点(按时间顺序) - -### 1. axios 不可用 -QZ Music v2 运行环境没有 `axios`,必须用 Node.js 内置 `http`/`https` 模块。 - -### 2. 插件格式演变 -- 最初直接写 `module.exports = { ... }`,后来发现 app 期望 `plugin.musicSearch.search(...)` 这样的对象方法格式。 -- 12-4 版本之前尝试过 folder + plugin.json + index.js 结构,但用户最终确认只要单 `.js` 文件。 -- `pluginInfo` 必须内嵌在 `module.exports` 里,包含 `info`、`env`、`ext`、`quality`、`supportFunc`。 - -### 3. 环境变量读取 -用 `global.env`,不是 `process.env`。 - +## 浜屻€侀噸澶ц俯鍧戠偣锛堟寜鏃堕棿椤哄簭锛? +### 1. axios 涓嶅彲鐢?QZ Music v2 杩愯鐜娌℃湁 `axios`锛屽繀椤荤敤 Node.js 鍐呯疆 `http`/`https` 妯″潡銆? +### 2. 鎻掍欢鏍煎紡婕斿彉 +- 鏈€鍒濈洿鎺ュ啓 `module.exports = { ... }`锛屽悗鏉ュ彂鐜?app 鏈熸湜 `plugin.musicSearch.search(...)` 杩欐牱鐨勫璞℃柟娉曟牸寮忋€?- 12-4 鐗堟湰涔嬪墠灏濊瘯杩?folder + plugin.json + index.js 缁撴瀯锛屼絾鐢ㄦ埛鏈€缁堢‘璁ゅ彧瑕佸崟 `.js` 鏂囦欢銆?- `pluginInfo` 蹇呴』鍐呭祵鍦?`module.exports` 閲岋紝鍖呭惈 `info`銆乣env`銆乣ext`銆乣quality`銆乣supportFunc`銆? +### 3. 鐜鍙橀噺璇诲彇 +鐢?`global.env`锛屼笉鏄?`process.env`銆? ```js var env = global.env || {} var CERU_KEY = env.ceru_key || '' ``` -### 4. Javet / V8 语法兼容性(导致"闪退") -必须使用最保守的 ES5 风格语法: -- `var` 代替 `let/const` -- 传统 `function` 声明,不用箭头函数 -- 不用 `async/await`,只用 Promise 链式调用 -- `catch` 必须带参数:`catch (e) { }`,不能写 `catch { }` -- 不用 `Promise.allSettled`,自己用 `Promise.all + .then/.catch` 包装 -- 不用对象解构等现代语法 - -### 5. 搜索结果字段必须严格匹配 -app 的 `MusicListResponse` 要求返回: - +### 4. Javet / V8 璇硶鍏煎鎬э紙瀵艰嚧"闂€€"锛?蹇呴』浣跨敤鏈€淇濆畧鐨?ES5 椋庢牸璇硶锛?- `var` 浠f浛 `let/const` +- 浼犵粺 `function` 澹版槑锛屼笉鐢ㄧ澶村嚱鏁?- 涓嶇敤 `async/await`锛屽彧鐢?Promise 閾惧紡璋冪敤 +- `catch` 蹇呴』甯﹀弬鏁帮細`catch (e) { }`锛屼笉鑳藉啓 `catch { }` +- 涓嶇敤 `Promise.allSettled`锛岃嚜宸辩敤 `Promise.all + .then/.catch` 鍖呰 +- 涓嶇敤瀵硅薄瑙f瀯绛夌幇浠h娉? +### 5. 鎼滅储缁撴灉瀛楁蹇呴』涓ユ牸鍖归厤 +app 鐨?`MusicListResponse` 瑕佹眰杩斿洖锛? ```js { list: [...], @@ -60,24 +43,17 @@ app 的 `MusicListResponse` 要求返回: } ``` -单首歌曲字段要包含: -- `id`:歌曲唯一标识 -- `name`:歌名 -- `artists`:歌手名(用 `/` 或 `、`分隔) -- `albumName` / `albumId` -- `pic`、`mPic`、`sPic`:封面图 -- `interval`:时长字符串 `m:ss` -- `qualities`:音质大小映射 -- `source`:平台标识 - -网易云搜索之前用 `{ songs: [], total: 0 }` 导致报错:`Field 'list' is required for type ...`。 - -### 6. 音质参数格式 -HUIBQ 等第三方 API 要求音质参数带 `k`,如 `320k`、`128k`、`999k`。 -`mapBr` 返回 `'320'` 时返回的是 fallback 无效 URL,导致所有平台播放失败。 - -### 7. 网易云封面图 -网易云搜索 API 返回的 `album.picId` 是数字,需要 Base64 编码后拼接: +鍗曢姝屾洸瀛楁瑕佸寘鍚細 +- `id`锛氭瓕鏇插敮涓€鏍囪瘑 +- `name`锛氭瓕鍚?- `artists`锛氭瓕鎵嬪悕锛堢敤 `/` 鎴?`銆乣鍒嗛殧锛?- `albumName` / `albumId` +- `pic`銆乣mPic`銆乣sPic`锛氬皝闈㈠浘 +- `interval`锛氭椂闀垮瓧绗︿覆 `m:ss` +- `qualities`锛氶煶璐ㄥぇ灏忔槧灏?- `source`锛氬钩鍙版爣璇? +缃戞槗浜戞悳绱箣鍓嶇敤 `{ songs: [], total: 0 }` 瀵艰嚧鎶ラ敊锛歚Field 'list' is required for type ...`銆? +### 6. 闊宠川鍙傛暟鏍煎紡 +HUIBQ 绛夌涓夋柟 API 瑕佹眰闊宠川鍙傛暟甯?`k`锛屽 `320k`銆乣128k`銆乣999k`銆?`mapBr` 杩斿洖 `'320'` 鏃惰繑鍥炵殑鏄?fallback 鏃犳晥 URL锛屽鑷存墍鏈夊钩鍙版挱鏀惧け璐ャ€? +### 7. 缃戞槗浜戝皝闈㈠浘 +缃戞槗浜戞悳绱?API 杩斿洖鐨?`album.picId` 鏄暟瀛楋紝闇€瑕?Base64 缂栫爜鍚庢嫾鎺ワ細 ```js var picIdStr = String(s.album.picId) @@ -85,14 +61,9 @@ var picIdB64 = Buffer.from(picIdStr).toString('base64').replace(/=/g, '') var pic = 'https://p2.music.126.net/' + picIdB64 + '/' + picIdStr + '.jpg' ``` -### 8. 酷狗搜索接口字段名 -`mobilecdn.kugou.com` 接口返回的字段是 `errcode` 不是 `error_code`,且歌曲信息字段为 `hash`、`songname`、`singername`、`imgurl`。 - -### 9. 酷狗封面图 -最初错误地用了酷我的 `img2.kuwo.cn` 域名。应使用酷狗搜索接口返回的 `imgurl`,替换 `{size}` 为 `400`。 - -### 10. 测速容灾模式 -所有平台 `getUrl` 采用并发请求多个 API,取第一个成功结果: +### 8. 閰风嫍鎼滅储鎺ュ彛瀛楁鍚?`mobilecdn.kugou.com` 鎺ュ彛杩斿洖鐨勫瓧娈垫槸 `errcode` 涓嶆槸 `error_code`锛屼笖姝屾洸淇℃伅瀛楁涓?`hash`銆乣songname`銆乣singername`銆乣imgurl`銆? +### 9. 閰风嫍灏侀潰鍥?鏈€鍒濋敊璇湴鐢ㄤ簡閰锋垜鐨?`img2.kuwo.cn` 鍩熷悕銆傚簲浣跨敤閰风嫍鎼滅储鎺ュ彛杩斿洖鐨?`imgurl`锛屾浛鎹?`{size}` 涓?`400`銆? +### 10. 娴嬮€熷鐏炬ā寮?鎵€鏈夊钩鍙?`getUrl` 閲囩敤骞跺彂璇锋眰澶氫釜 API锛屽彇绗竴涓垚鍔熺粨鏋滐細 ```js Promise.all(promises.map(function(p) { @@ -106,124 +77,116 @@ Promise.all(promises.map(function(p) { }) ``` -### 11. 版本号管理 -每次修改所有插件统一升级版本号。当前最终版本:**0.0.3**。 - +### 11. 鐗堟湰鍙风鐞?姣忔淇敼鎵€鏈夋彃浠剁粺涓€鍗囩骇鐗堟湰鍙枫€傚綋鍓嶆渶缁堢増鏈細**0.0.3**銆? --- -## 三、环境变量说明 - -| key | 用途 | 必填 | +## 涓夈€佺幆澧冨彉閲忚鏄? +| key | 鐢ㄩ€?| 蹇呭~ | |-----|------|------| -| `ceru_key` | 聆澜音源 API 密钥 | 否 | -| `playlist_url` | 网易云个人主页链接,用于 `userPlaylist` | 否 | -| `cookie` | 网易云 Cookie,用于搜索和 ext 功能 | 否 | +| `ceru_key` | 鑱嗘緶闊虫簮 API 瀵嗛挜 | 鍚?| +| `playlist_url` | 缃戞槗浜戜釜浜轰富椤甸摼鎺ワ紝鐢ㄤ簬 `userPlaylist` | 鍚?| +| `cookie` | 缃戞槗浜?Cookie锛岀敤浜庢悳绱㈠拰 ext 鍔熻兘 | 鍚?| --- -## 四、所有相关链接/API +## 鍥涖€佹墍鏈夌浉鍏抽摼鎺?API -### 4.1 文档/参考链接 -- QZ Music v2 插件文档(用户自维护,非公开链接) -- CSDN 网易云搜索 API:https://blog.csdn.net/2301_79279502/article/details/135568447 -- CSDN 酷狗 API 整理:https://blog.csdn.net/2301_78245299/article/details/140352615 -- 旧版插件参考:`Koneko_酷狗音乐_v1.0.9.js` -- 旧日志:`log_2026-06-19_0.txt` +### 4.1 鏂囨。/鍙傝€冮摼鎺?- QZ Music v2 鎻掍欢鏂囨。锛堢敤鎴疯嚜缁存姢锛岄潪鍏紑閾炬帴锛?- CSDN 缃戞槗浜戞悳绱?API锛歨ttps://blog.csdn.net/2301_79279502/article/details/135568447 +- CSDN 閰风嫍 API 鏁寸悊锛歨ttps://blog.csdn.net/2301_78245299/article/details/140352615 +- 鏃х増鎻掍欢鍙傝€冿細`Koneko_閰风嫍闊充箰_v1.0.9.js` +- 鏃ф棩蹇楋細`log_2026-06-19_0.txt` -### 4.2 QQ音乐相关 API -- QQ音乐搜索签名接口:`https://u.y.qq.com/cgi-bin/musics.fcg?sign={zzcSign}` -- QQ音乐搜索 POST:`https://u.y.qq.com/cgi-bin/musics.fcg?sign=` -- QQ音乐热词:`https://u.y.qq.com/cgi-bin/musicu.fcg` -- QQ音乐搜索建议:`https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg` -- QQ音乐歌手封面:`https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` -- QQ音乐专辑封面:`https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumMid}.jpg` -- 聆澜 QQ 音源:`https://source.shiqianjiang.cn/api/music/url?source=tx&songId={id}&quality={q}` -- HUIBQ QQ 音源:`https://lxmusicapi.onrender.com/url/tx/{id}/{q}` -- 忆音 QQ 音源:`https://music.3e0.cn/?server=tencent&type=url&id={id}` -- 星海 QQ 音源:`https://music-api.gdstudio.xyz/api.php?types=url&source=tencent&id={id}&br={br}` -- 收集 QQ 音源:`https://cyapi.top/API/qq_music.php?apikey=...&type=json&mid={id}` -- 念心 QQ 音源:`https://music.nxinxz.com/kgqq/tx.php?id={id}&level={q}&type=mp3` -- 长青 QQ 音源:`http://175.27.166.236/kgqq/qq.php?type=mp3&id={id}&level={q}` -- 星海备 QQ 音源:`https://music-dl.sayqz.com/api/?source=qq&id={id}&type=url&br={q}` -- fish QQ 音源:`https://m-api.ceseet.me/url/tx/{id}/{q}` -- HYW QQ 音源:`https://music.bxa241d4.shop/api/music/url?source=tx&songId={id}&quality={q}` +### 4.2 QQ闊充箰鐩稿叧 API +- QQ闊充箰鎼滅储绛惧悕鎺ュ彛锛歚https://u.y.qq.com/cgi-bin/musics.fcg?sign={zzcSign}` +- QQ闊充箰鎼滅储 POST锛歚https://u.y.qq.com/cgi-bin/musics.fcg?sign=` +- QQ闊充箰鐑瘝锛歚https://u.y.qq.com/cgi-bin/musicu.fcg` +- QQ闊充箰鎼滅储寤鸿锛歚https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg` +- QQ闊充箰姝屾墜灏侀潰锛歚https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` +- QQ闊充箰涓撹緫灏侀潰锛歚https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumMid}.jpg` +- 鑱嗘緶 QQ 闊虫簮锛歚https://source.shiqianjiang.cn/api/music/url?source=tx&songId={id}&quality={q}` +- HUIBQ QQ 闊虫簮锛歚https://lxmusicapi.onrender.com/url/tx/{id}/{q}` +- 蹇嗛煶 QQ 闊虫簮锛歚https://music.3e0.cn/?server=tencent&type=url&id={id}` +- 鏄熸捣 QQ 闊虫簮锛歚https://music-api.gdstudio.xyz/api.php?types=url&source=tencent&id={id}&br={br}` +- 鏀堕泦 QQ 闊虫簮锛歚https://cyapi.top/API/qq_music.php?apikey=...&type=json&mid={id}` +- 蹇靛績 QQ 闊虫簮锛歚https://music.nxinxz.com/kgqq/tx.php?id={id}&level={q}&type=mp3` +- 闀块潚 QQ 闊虫簮锛歚http://175.27.166.236/kgqq/qq.php?type=mp3&id={id}&level={q}` +- 鏄熸捣澶?QQ 闊虫簮锛歚https://music-dl.sayqz.com/api/?source=qq&id={id}&type=url&br={q}` +- fish QQ 闊虫簮锛歚https://m-api.ceseet.me/url/tx/{id}/{q}` +- HYW QQ 闊虫簮锛歚https://music.bxa241d4.shop/api/music/url?source=tx&songId={id}&quality={q}` -### 4.3 酷狗音乐相关 API -- 酷狗搜索(mobilecdn):`http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword={kw}&page={p}&pagesize={n}` -- 酷狗搜索(songsearch):`https://songsearch.kugou.com/song_search_v2?keyword={kw}&page={p}&pagesize={n}&...` -- 酷狗搜索建议:`https://searchtip.kugou.com/getSearchTip?MusicTipCount=10&keyword={kw}` -- 酷狗热词:`http://gateway.kugou.com/api/v3/search/hot_tab?...` -- 聆澜 酷狗音源:`https://source.shiqianjiang.cn/api/music/url?source=kg&songId={id}&quality={q}` -- HUIBQ 酷狗音源:`https://lxmusicapi.onrender.com/url/kg/{id}/{q}` -- 星海 酷狗音源:`https://music-api.gdstudio.xyz/api.php?types=url&source=kugou&id={id}&br={br}` -- 念心 酷狗音源:`https://music.nxinxz.com/kgqq/kg.php?id={id}&level={q}&type=mp3` -- 长青 酷狗音源:`https://music.haitangw.cc/kgqq/kg.php?id={id}&level={q}&type=mp3` -- 星海备 酷狗音源:`https://music-dl.sayqz.com/api/?source=kugou&id={id}&type=url&br={q}` -- fish 酷狗音源:`https://m-api.ceseet.me/url/kg/{id}/{q}` -- HYW 酷狗音源:`https://music.bxa241d4.shop/api/music/url?source=kg&songId={id}&quality={q}` +### 4.3 閰风嫍闊充箰鐩稿叧 API +- 閰风嫍鎼滅储锛坢obilecdn锛夛細`http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword={kw}&page={p}&pagesize={n}` +- 閰风嫍鎼滅储锛坰ongsearch锛夛細`https://songsearch.kugou.com/song_search_v2?keyword={kw}&page={p}&pagesize={n}&...` +- 閰风嫍鎼滅储寤鸿锛歚https://searchtip.kugou.com/getSearchTip?MusicTipCount=10&keyword={kw}` +- 閰风嫍鐑瘝锛歚http://gateway.kugou.com/api/v3/search/hot_tab?...` +- 鑱嗘緶 閰风嫍闊虫簮锛歚https://source.shiqianjiang.cn/api/music/url?source=kg&songId={id}&quality={q}` +- HUIBQ 閰风嫍闊虫簮锛歚https://lxmusicapi.onrender.com/url/kg/{id}/{q}` +- 鏄熸捣 閰风嫍闊虫簮锛歚https://music-api.gdstudio.xyz/api.php?types=url&source=kugou&id={id}&br={br}` +- 蹇靛績 閰风嫍闊虫簮锛歚https://music.nxinxz.com/kgqq/kg.php?id={id}&level={q}&type=mp3` +- 闀块潚 閰风嫍闊虫簮锛歚https://music.haitangw.cc/kgqq/kg.php?id={id}&level={q}&type=mp3` +- 鏄熸捣澶?閰风嫍闊虫簮锛歚https://music-dl.sayqz.com/api/?source=kugou&id={id}&type=url&br={q}` +- fish 閰风嫍闊虫簮锛歚https://m-api.ceseet.me/url/kg/{id}/{q}` +- HYW 閰风嫍闊虫簮锛歚https://music.bxa241d4.shop/api/music/url?source=kg&songId={id}&quality={q}` -### 4.4 酷我音乐相关 API -- 酷我搜索:`http://search.kuwo.cn/r.s?client=kt&all={kw}&pn={p}&rn={n}&...` -- 酷我搜索建议:`https://tips.kuwo.cn/t.s?...&w={kw}&...` -- 酷我热词:`http://hotword.kuwo.cn/hotword.s?...` -- 酷我专辑封面:`https://img2.kuwo.cn/star/albumcover/300/{albumId}.jpg` -- 酷我歌手封面:`http://artistpicserver.kuwo.cn/pic.web?...&rid={rid}` -- 聆澜 酷我音源:`https://source.shiqianjiang.cn/api/music/url?source=kw&songId={id}&quality={q}` -- HUIBQ 酷我音源:`https://lxmusicapi.onrender.com/url/kw/{id}/{q}` -- 星海 酷我音源:`https://music-api.gdstudio.xyz/api.php?types=url&source=kuwo&id={id}&br={br}` -- 收集 KW 音源:`https://kw-api.cenguigui.cn/api/song/url?id={id}&quality={q}` -- 念心 酷我音源:`https://music.nxinxz.com/kgqq/kw.php?id={id}&level={q}&type=mp3` -- 长青 酷我音源:`https://musicapi.haitangw.net/music/kw.php?id={id}&level={q}&type=mp3` -- 星海备 酷我音源:`https://music-dl.sayqz.com/api/?source=kuwo&id={id}&type=url&br={q}` -- fish 酷我音源:`https://m-api.ceseet.me/url/kw/{id}/{q}` -- HYW 酷我音源:`https://music.bxa241d4.shop/api/music/url?source=kw&songId={id}&quality={q}` +### 4.4 閰锋垜闊充箰鐩稿叧 API +- 閰锋垜鎼滅储锛歚http://search.kuwo.cn/r.s?client=kt&all={kw}&pn={p}&rn={n}&...` +- 閰锋垜鎼滅储寤鸿锛歚https://tips.kuwo.cn/t.s?...&w={kw}&...` +- 閰锋垜鐑瘝锛歚http://hotword.kuwo.cn/hotword.s?...` +- 閰锋垜涓撹緫灏侀潰锛歚https://img2.kuwo.cn/star/albumcover/300/{albumId}.jpg` +- 閰锋垜姝屾墜灏侀潰锛歚http://artistpicserver.kuwo.cn/pic.web?...&rid={rid}` +- 鑱嗘緶 閰锋垜闊虫簮锛歚https://source.shiqianjiang.cn/api/music/url?source=kw&songId={id}&quality={q}` +- HUIBQ 閰锋垜闊虫簮锛歚https://lxmusicapi.onrender.com/url/kw/{id}/{q}` +- 鏄熸捣 閰锋垜闊虫簮锛歚https://music-api.gdstudio.xyz/api.php?types=url&source=kuwo&id={id}&br={br}` +- 鏀堕泦 KW 闊虫簮锛歚https://kw-api.cenguigui.cn/api/song/url?id={id}&quality={q}` +- 蹇靛績 閰锋垜闊虫簮锛歚https://music.nxinxz.com/kgqq/kw.php?id={id}&level={q}&type=mp3` +- 闀块潚 閰锋垜闊虫簮锛歚https://musicapi.haitangw.net/music/kw.php?id={id}&level={q}&type=mp3` +- 鏄熸捣澶?閰锋垜闊虫簮锛歚https://music-dl.sayqz.com/api/?source=kuwo&id={id}&type=url&br={q}` +- fish 閰锋垜闊虫簮锛歚https://m-api.ceseet.me/url/kw/{id}/{q}` +- HYW 閰锋垜闊虫簮锛歚https://music.bxa241d4.shop/api/music/url?source=kw&songId={id}&quality={q}` -### 4.5 网易云音乐相关 API -- 网易云搜索(GET):`https://music.163.com/api/search/get/web?csrf_token=&hlposttag=&s={kw}&type=1&offset={o}&total=true&limit={n}` -- 网易云搜索(weapi POST,备用):`https://music.163.com/weapi/cloudsearch/get/web` -- 网易云热词(weapi):`https://music.163.com/weapi/search/hot` -- 网易云官方播放链接(weapi):`https://music.163.com/weapi/song/enhance/player/url` -- 网易云用户歌单(weapi):`https://music.163.com/weapi/user/playlist` -- 网易云每日推荐(weapi):`https://music.163.com/weapi/v1/discovery/recommend/songs` -- 网易云私人 FM(weapi):`https://music.163.com/weapi/v1/radio/get` -- 网易云喜欢歌曲(weapi):`https://music.163.com/weapi/song/like/get` -- 网易云歌曲详情(weapi):`https://music.163.com/weapi/v3/song/detail` -- 网易云封面图:`https://p2.music.126.net/{base64(picId)}/{picId}.jpg` -- 聆澜 网易云音源:`https://ceruapi.lol/meting-api-0/?server=netease&type=url&id={id}&auth={key}&br={br}` -- bb 网易云音源:`https://api.bbdcz.cn/music/netease/url?id={id}&br={br}` -- lx 网易云音源:`https://lxmusicapi.onrender.com/url/wy/{id}/{br}` -- ymc 网易云音源:`https://api.ymusic.icu/netease/song?id={id}&quality={br}` -- unms 网易云音源:`https://unms.zeabur.app/netease/url?id={id}&br={br}` +### 4.5 缃戞槗浜戦煶涔愮浉鍏?API +- 缃戞槗浜戞悳绱紙GET锛夛細`https://music.163.com/api/search/get/web?csrf_token=&hlposttag=&s={kw}&type=1&offset={o}&total=true&limit={n}` +- 缃戞槗浜戞悳绱紙weapi POST锛屽鐢級锛歚https://music.163.com/weapi/cloudsearch/get/web` +- 缃戞槗浜戠儹璇嶏紙weapi锛夛細`https://music.163.com/weapi/search/hot` +- 缃戞槗浜戝畼鏂规挱鏀鹃摼鎺ワ紙weapi锛夛細`https://music.163.com/weapi/song/enhance/player/url` +- 缃戞槗浜戠敤鎴锋瓕鍗曪紙weapi锛夛細`https://music.163.com/weapi/user/playlist` +- 缃戞槗浜戞瘡鏃ユ帹鑽愶紙weapi锛夛細`https://music.163.com/weapi/v1/discovery/recommend/songs` +- 缃戞槗浜戠浜?FM锛坵eapi锛夛細`https://music.163.com/weapi/v1/radio/get` +- 缃戞槗浜戝枩娆㈡瓕鏇诧紙weapi锛夛細`https://music.163.com/weapi/song/like/get` +- 缃戞槗浜戞瓕鏇茶鎯咃紙weapi锛夛細`https://music.163.com/weapi/v3/song/detail` +- 缃戞槗浜戝皝闈㈠浘锛歚https://p2.music.126.net/{base64(picId)}/{picId}.jpg` +- 鑱嗘緶 缃戞槗浜戦煶婧愶細`https://ceruapi.lol/meting-api-0/?server=netease&type=url&id={id}&auth={key}&br={br}` +- bb 缃戞槗浜戦煶婧愶細`https://api.bbdcz.cn/music/netease/url?id={id}&br={br}` +- lx 缃戞槗浜戦煶婧愶細`https://lxmusicapi.onrender.com/url/wy/{id}/{br}` +- ymc 缃戞槗浜戦煶婧愶細`https://api.ymusic.icu/netease/song?id={id}&quality={br}` +- unms 缃戞槗浜戦煶婧愶細`https://unms.zeabur.app/netease/url?id={id}&br={br}` -### 4.6 咪咕音乐相关 API -- 咪咕搜索:`https://jadeite.migu.cn/music_search/v3/search/searchAll?...` -- 聆澜 咪咕音源:`https://source.shiqianjiang.cn/api/music/url?source=mg&songId={id}&quality={q}` -- HUIBQ 咪咕音源:`https://lxmusicapi.onrender.com/url/mg/{id}/{q}` -- 星海 咪咕音源:`https://music-api.gdstudio.xyz/api.php?types=url&source=migu&id={id}&br={br}` -- 念心 咪咕音源:`https://music.nxinxz.com/kgqq/mg.php?id={id}&level={q}&type=mp3` -- 长青 咪咕音源:`https://music.haitangw.cc/musicapi/mg.php?id={id}&level={q}&type=mp3` -- 星海备 咪咕音源:`https://music-dl.sayqz.com/api/?source=migu&id={id}&type=url&br={q}` -- fish 咪咕音源:`https://m-api.ceseet.me/url/mg/{id}/{q}` -- HYW 咪咕音源:`https://music.bxa241d4.shop/api/music/url?source=mg&songId={id}&quality={q}` +### 4.6 鍜挄闊充箰鐩稿叧 API +- 鍜挄鎼滅储锛歚https://jadeite.migu.cn/music_search/v3/search/searchAll?...` +- 鑱嗘緶 鍜挄闊虫簮锛歚https://source.shiqianjiang.cn/api/music/url?source=mg&songId={id}&quality={q}` +- HUIBQ 鍜挄闊虫簮锛歚https://lxmusicapi.onrender.com/url/mg/{id}/{q}` +- 鏄熸捣 鍜挄闊虫簮锛歚https://music-api.gdstudio.xyz/api.php?types=url&source=migu&id={id}&br={br}` +- 蹇靛績 鍜挄闊虫簮锛歚https://music.nxinxz.com/kgqq/mg.php?id={id}&level={q}&type=mp3` +- 闀块潚 鍜挄闊虫簮锛歚https://music.haitangw.cc/musicapi/mg.php?id={id}&level={q}&type=mp3` +- 鏄熸捣澶?鍜挄闊虫簮锛歚https://music-dl.sayqz.com/api/?source=migu&id={id}&type=url&br={q}` +- fish 鍜挄闊虫簮锛歚https://m-api.ceseet.me/url/mg/{id}/{q}` +- HYW 鍜挄闊虫簮锛歚https://music.bxa241d4.shop/api/music/url?source=mg&songId={id}&quality={q}` -### 4.7 GIT音源相关 API -- 聆澜 GIT 音源:`https://source.shiqianjiang.cn/api/music/url?source=git&songId={id}&quality={q}` -- HUIBQ GIT 音源:`https://lxmusicapi.onrender.com/url/git/{id}/{q}` +### 4.7 GIT闊虫簮鐩稿叧 API +- 鑱嗘緶 GIT 闊虫簮锛歚https://source.shiqianjiang.cn/api/music/url?source=git&songId={id}&quality={q}` +- HUIBQ GIT 闊虫簮锛歚https://lxmusicapi.onrender.com/url/git/{id}/{q}` -### 4.8 其他提及但未使用/备用 -- `https://music.163.com/weapi` 系列(weapi 备用) -- `https://m.kugou.com/rank/list&json=true` +### 4.8 鍏朵粬鎻愬強浣嗘湭浣跨敤/澶囩敤 +- `https://music.163.com/weapi` 绯诲垪锛坵eapi 澶囩敤锛?- `https://m.kugou.com/rank/list&json=true` - `http://mobilecdnbj.kugou.com/api/v3/tag/list?pid=0&apiver=2&plat=0` --- -## 五、最终代码 - -> 以下 6 个文件版本号均为 0.0.3,是截至 2026-06-19 的最终可用代码。 - +## 浜斻€佹渶缁堜唬鐮? +> 浠ヤ笅 6 涓枃浠剁増鏈彿鍧囦负 0.0.3锛屾槸鎴嚦 2026-06-19 鐨勬渶缁堝彲鐢ㄤ唬鐮併€? --- -## Koneko_QQ音乐_v0.0.3 +## Koneko_QQ闊充箰_v0.0.3 ```js 'use strict' @@ -325,7 +288,7 @@ function formatSingerName(singers) { for (var i = 0; i < singers.length; i++) { if (singers[i].name) names.push(singers[i].name) } - return names.join('、') + return names.join('銆?) } function formatSize(bytes) { @@ -343,7 +306,7 @@ var musicSearch = { musicSearch: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) var data = { comm: { ct: '11', cv: '14090508', v: '14090508', tmeAppID: 'qqmusic', @@ -380,7 +343,7 @@ var musicSearch = { var albumName = '' if (item.album) { albumName = item.album.name; albumId = item.album.mid } var picUrl = '' - if (albumId === '' || albumId === '空') { + if (albumId === '' || albumId === '绌?) { if (item.singer && item.singer.length) { picUrl = 'https://y.gtimg.cn/music/photo_new/T001R500x500M000' + item.singer[0].mid + '.jpg' } @@ -490,7 +453,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=tx&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -504,7 +467,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '忆音', + name: '蹇嗛煶', url: 'https://music.3e0.cn/?server=tencent&type=url&id=' + songId, headers: HEADERS_COMMON, extract: function(res) { @@ -514,7 +477,7 @@ function buildApis(songId, q) { } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=tencent&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -524,7 +487,7 @@ function buildApis(songId, q) { } }, { - name: '收集QQ', + name: '鏀堕泦QQ', url: 'https://cyapi.top/API/qq_music.php?apikey=4d6f7369632d6170692e63656e6775696769692e636f6d&type=json&mid=' + songId, headers: HEADERS_COMMON, extract: function(res) { @@ -534,7 +497,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/tx.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -544,7 +507,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'http://175.27.166.236/kgqq/qq.php?type=mp3&id=' + songId + '&level=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -554,7 +517,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=qq&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -597,12 +560,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko QQ音乐] ' + api.name + ' 成功') + console.log('[Koneko QQ闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko QQ音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko QQ闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -614,7 +577,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko QQ音乐] 所有API均失败') + console.error('[Koneko QQ闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -639,13 +602,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_tx', name: 'QQ音乐 - Koneko', version: '0.0.3', description: 'QQ音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_tx', name: 'QQ闊充箰 - Koneko', version: '0.0.3', description: 'QQ闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] @@ -671,7 +634,7 @@ module.exports = { --- -## Koneko_酷狗音乐_v0.0.3 +## Koneko_閰风嫍闊充箰_v0.0.3 ```js 'use strict' @@ -721,7 +684,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit return httpGet( @@ -813,7 +776,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=kg&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -827,7 +790,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=kugou&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -837,7 +800,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/kg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -847,7 +810,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://music.haitangw.cc/kgqq/kg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -857,7 +820,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=kugou&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -900,12 +863,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 酷狗音乐] ' + api.name + ' 成功') + console.log('[Koneko 閰风嫍闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 酷狗音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 閰风嫍闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -917,7 +880,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 酷狗音乐] 所有API均失败') + console.error('[Koneko 閰风嫍闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -942,13 +905,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_kg', name: '酷狗音乐 - Koneko', version: '0.0.3', description: '酷狗音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_kg', name: '閰风嫍闊充箰 - Koneko', version: '0.0.3', description: '閰风嫍闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] @@ -974,7 +937,7 @@ module.exports = { --- -## Koneko_酷我音乐_v0.0.3 +## Koneko_閰锋垜闊充箰_v0.0.3 ```js 'use strict' @@ -1024,7 +987,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit return httpGet( @@ -1059,7 +1022,7 @@ var musicSearch = { picUrl = 'http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=' + songId } var artistStr = '' - if (info.ARTIST) artistStr = info.ARTIST.replace(/&/g, '、') + if (info.ARTIST) artistStr = info.ARTIST.replace(/&/g, '銆?) var duration = parseInt(info.DURATION) list.push({ id: String(songId), @@ -1124,7 +1087,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=kw&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -1138,7 +1101,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=kuwo&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -1148,7 +1111,7 @@ function buildApis(songId, q) { } }, { - name: '收集KW', + name: '鏀堕泦KW', url: 'https://kw-api.cenguigui.cn/api/song/url?id=' + songId + '&quality=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -1158,7 +1121,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/kw.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -1168,7 +1131,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://musicapi.haitangw.net/music/kw.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -1178,7 +1141,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=kuwo&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -1221,12 +1184,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 酷我音乐] ' + api.name + ' 成功') + console.log('[Koneko 閰锋垜闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 酷我音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 閰锋垜闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -1238,7 +1201,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 酷我音乐] 所有API均失败') + console.error('[Koneko 閰锋垜闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -1263,13 +1226,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_kw', name: '酷我音乐 - Koneko', version: '0.0.3', description: '酷我音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_kw', name: '閰锋垜闊充箰 - Koneko', version: '0.0.3', description: '閰锋垜闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] @@ -1295,7 +1258,7 @@ module.exports = { --- -## Koneko_网易云音乐_v0.0.3 +## Koneko_缃戞槗浜戦煶涔恄v0.0.3 ```js 'use strict' @@ -1484,7 +1447,7 @@ function wySearch(keyword, page, limit) { list.push({ id: s.id ? String(s.id) : '', name: s.name || '', - artists: artists.join('、'), + artists: artists.join('銆?), albumName: album, albumId: albumId, source: 'wy', @@ -1514,7 +1477,7 @@ function wyTipSearch(keyword) { var tips = [] for (var i = 0; i < res.result.songs.length; i++) { var s = res.result.songs[i] - tips.push(s.name + ' - ' + (s.artists && s.artists[0] ? s.artists[0].name : '未知')) + tips.push(s.name + ' - ' + (s.artists && s.artists[0] ? s.artists[0].name : '鏈煡')) } return tips }).catch(function(e) { @@ -1779,26 +1742,26 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_wy', name: '网易云音乐 - Koneko', version: '0.0.3', description: '网易云音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, + info: { id: 'koneko_wy', name: '缃戞槗浜戦煶涔?- Koneko', version: '0.0.3', description: '缃戞槗浜戦煶涔愯仛鍚堥煶婧愭彃浠讹紝瀹樻柟鎼滅储+澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, env: [ - { key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }, - { key: 'playlist_url', name: '个人主页链接', description: '网易云音乐个人主页链接,用于获取个人歌单' }, - { key: 'cookie', name: 'Cookie', description: '网易云音乐Cookie,用于搜索/每日推荐/私人FM/我喜欢的音乐' } + { key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }, + { key: 'playlist_url', name: '涓汉涓婚〉閾炬帴', description: '缃戞槗浜戦煶涔愪釜浜轰富椤甸摼鎺ワ紝鐢ㄤ簬鑾峰彇涓汉姝屽崟' }, + { key: 'cookie', name: 'Cookie', description: '缃戞槗浜戦煶涔怌ookie锛岀敤浜庢悳绱?姣忔棩鎺ㄨ崘/绉佷汉FM/鎴戝枩娆㈢殑闊充箰' } ], ext: [ - { name: '个人歌单', description: '通过分享链接获取个人歌单', entry: 'plugin.userPlaylist()', type: 'playlists' }, - { name: '每日推荐', description: '获取每日推荐歌曲', entry: 'plugin.dailyRecommend()', type: 'songs' }, - { name: '私人FM', description: '获取私人FM歌曲', entry: 'plugin.personalFm()', type: 'songs' }, - { name: '我喜欢的音乐', description: '获取我喜欢的音乐列表', entry: 'plugin.myLikedSongs()', type: 'songs' } + { name: '涓汉姝屽崟', description: '閫氳繃鍒嗕韩閾炬帴鑾峰彇涓汉姝屽崟', entry: 'plugin.userPlaylist()', type: 'playlists' }, + { name: '姣忔棩鎺ㄨ崘', description: '鑾峰彇姣忔棩鎺ㄨ崘姝屾洸', entry: 'plugin.dailyRecommend()', type: 'songs' }, + { name: '绉佷汉FM', description: '鑾峰彇绉佷汉FM姝屾洸', entry: 'plugin.personalFm()', type: 'songs' }, + { name: '鎴戝枩娆㈢殑闊充箰', description: '鑾峰彇鎴戝枩娆㈢殑闊充箰鍒楄〃', entry: 'plugin.myLikedSongs()', type: 'songs' } ], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' }, - { name: '高清环绕声', ui: 'DB', id: 'jyeffect' }, - { name: '沉浸环绕声', ui: 'SK', id: 'sky' }, - { name: '超清母带', ui: 'MT', id: 'jymaster' } + { name: '楂樻竻鐜粫澹?, ui: 'DB', id: 'jyeffect' }, + { name: '娌夋蹈鐜粫澹?, ui: 'SK', id: 'sky' }, + { name: '瓒呮竻姣嶅甫', ui: 'MT', id: 'jymaster' } ], supportFunc: [] } @@ -1877,7 +1840,7 @@ module.exports = { --- -## Koneko_咪咕音乐_v0.0.3 +## Koneko_鍜挄闊充箰_v0.0.3 ```js 'use strict' @@ -1942,7 +1905,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit var time = Date.now().toString() @@ -1992,7 +1955,7 @@ var musicSearch = { for (var k = 0; k < data.singerList.length; k++) { if (data.singerList[k].name) names.push(data.singerList[k].name) } - artists = names.join('、') + artists = names.join('銆?) } list.push({ artists: artists, @@ -2027,7 +1990,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=mg&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -2041,7 +2004,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=migu&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -2051,7 +2014,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/mg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -2061,7 +2024,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://music.haitangw.cc/musicapi/mg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -2071,7 +2034,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=migu&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -2114,12 +2077,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 咪咕音乐] ' + api.name + ' 成功') + console.log('[Koneko 鍜挄闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 咪咕音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 鍜挄闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -2131,7 +2094,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 咪咕音乐] 所有API均失败') + console.error('[Koneko 鍜挄闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -2156,13 +2119,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_mg', name: '咪咕音乐 - Koneko', version: '0.0.3', description: '咪咕音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_mg', name: '鍜挄闊充箰 - Koneko', version: '0.0.3', description: '鍜挄闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] @@ -2188,7 +2151,7 @@ module.exports = { --- -## Koneko_GIT音源_v0.0.3 +## Koneko_GIT闊虫簮_v0.0.3 ```js 'use strict' @@ -2221,7 +2184,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=git&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -2246,12 +2209,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko GIT音源] ' + api.name + ' 成功') + console.log('[Koneko GIT闊虫簮] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko GIT音源] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko GIT闊虫簮] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -2263,7 +2226,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko GIT音源] 所有API均失败') + console.error('[Koneko GIT闊虫簮] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -2295,13 +2258,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_git', name: 'GIT音源 - Koneko', version: '0.0.3', description: 'GIT音源聚合插件,聚合2+API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_git', name: 'GIT闊虫簮 - Koneko', version: '0.0.3', description: 'GIT闊虫簮鑱氬悎鎻掍欢锛岃仛鍚?+API闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/6a2d8996cc974039d1dfbbf7_Koneko插件开发避坑指南_v0.0.3.md b/6a2d8996cc974039d1dfbbf7_Koneko插件开发避坑指南_v0.0.3.md index b90092a..2d11aba 100644 --- a/6a2d8996cc974039d1dfbbf7_Koneko插件开发避坑指南_v0.0.3.md +++ b/6a2d8996cc974039d1dfbbf7_Koneko插件开发避坑指南_v0.0.3.md @@ -1,20 +1,16 @@ -# Koneko QZ Music v2 插件开发避坑指南 - -> 版本: 0.0.3 | 作者: 云汀(Miao-moe) | 目标: 支持到别的 AI 继续开发 - +# Koneko QZ Music v2 鎻掍欢寮€鍙戦伩鍧戞寚鍗? +> 鐗堟湰: 0.0.3 | 浣滆€? 浜戞眬(Miao-moe) | 鐩爣: 鏀寔鍒板埆鐨?AI 缁х画寮€鍙? --- -## 一、项目背景 +## 涓€銆侀」鐩儗鏅? +QZ Music v2 鏄竴娆?Android 闊充箰鎾斁鍣紝鏀寔閫氳繃**鎷撳睍鎻掍欢**鎺ュ叆澶氬钩鍙伴煶婧愩€傛彃浠剁郴缁熷熀浜?Node.js 杩愯鏃讹紙Javet/V8锛夛紝姣忎釜鎻掍欢鏄竴涓崟鐙殑 `.js` 鏂囦欢锛岄€氳繃 `module.exports` 瀵煎嚭鎺ュ彛銆? +### 1.1 鎻掍欢鍔犺浇鏈哄埗 -QZ Music v2 是一款 Android 音乐播放器,支持通过**拓展插件**接入多平台音源。插件系统基于 Node.js 运行时(Javet/V8),每个插件是一个单独的 `.js` 文件,通过 `module.exports` 导出接口。 +- canary 12-4 鐗堟湰涔嬪墠锛氱洿鎺ュ姞杞?`.js` 鏂囦欢 +- **canary 12-4 鍙婁箣鍚?*锛氶渶瑕?`鏂囦欢澶?+ plugin.json + index.js` 缁撴瀯锛堜絾鐢ㄦ埛瑕佹眰鍙敤 `.js`锛屾墍浠ュ綋鍓嶇増鏈槸鍗曟枃浠讹級 +- 杩愯鏃剁幆澧冨彉閲忛€氳繃 `global.env` 璁块棶锛?*涓嶆槸** `process.env` -### 1.1 插件加载机制 - -- canary 12-4 版本之前:直接加载 `.js` 文件 -- **canary 12-4 及之后**:需要 `文件夹 + plugin.json + index.js` 结构(但用户要求只用 `.js`,所以当前版本是单文件) -- 运行时环境变量通过 `global.env` 访问,**不是** `process.env` - -### 1.2 插件导出格式 +### 1.2 鎻掍欢瀵煎嚭鏍煎紡 ```js module.exports = { @@ -23,8 +19,7 @@ module.exports = { hotSearch: { getList: fn }, getUrl: fn, pluginInfo: { info: {...}, env: [...], ext: [...], quality: [...], supportFunc: [] }, - // 网易云特有 - userPlaylist: fn, + // 缃戞槗浜戠壒鏈? userPlaylist: fn, dailyRecommend: fn, personalFm: fn, myLikedSongs: fn @@ -33,33 +28,30 @@ module.exports = { --- -## 二、Javet/V8 兼容性大坑(最重要) - -QZ Music 使用 Javet 作为 JS 运行时(基于 V8),**不支持现代 ES 语法**,必须用保守写法: - -| 语法 | 是否支持 | 正确写法 | +## 浜屻€丣avet/V8 鍏煎鎬уぇ鍧戯紙鏈€閲嶈锛? +QZ Music 浣跨敤 Javet 浣滀负 JS 杩愯鏃讹紙鍩轰簬 V8锛夛紝**涓嶆敮鎸佺幇浠?ES 璇硶**锛屽繀椤荤敤淇濆畧鍐欐硶锛? +| 璇硶 | 鏄惁鏀寔 | 姝g‘鍐欐硶 | |------|---------|---------| -| `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` | -| `BigInt` 字面量 | ⚠️ 慎用 | `BigInt('0x' + hex)` | -| `class` | ❌ | 对象字面量 | -| 模板字符串 `${}` | ✅ | 可用 | -| `Buffer` | ✅ | Node.js 内置 | -| `require` | ✅ | CommonJS | +| `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` | +| `BigInt` 瀛楅潰閲?| 鈿狅笍 鎱庣敤 | `BigInt('0x' + hex)` | +| `class` | 鉂?| 瀵硅薄瀛楅潰閲?| +| 妯℃澘瀛楃涓?`${}` | 鉁?| 鍙敤 | +| `Buffer` | 鉁?| Node.js 鍐呯疆 | +| `require` | 鉁?| CommonJS | -### 2.1 Promise.allSettled 替代方案 +### 2.1 Promise.allSettled 鏇夸唬鏂规 ```js -// ❌ 不支持 -Promise.allSettled(promises) +// 鉂?涓嶆敮鎸?Promise.allSettled(promises) -// ✅ 正确写法 +// 鉁?姝g‘鍐欐硶 Promise.all(promises.map(function(p) { return p.then(function(v) { return { status: 'fulfilled', value: v } @@ -78,26 +70,23 @@ Promise.all(promises.map(function(p) { --- -## 三、搜索结果格式大坑 - -App 的 `MusicListResponse` 反序列化要求**必须有 `list` 字段**,不是 `songs`! - -### 3.1 正确的搜索返回格式 - +## 涓夈€佹悳绱㈢粨鏋滄牸寮忓ぇ鍧? +App 鐨?`MusicListResponse` 鍙嶅簭鍒楀寲瑕佹眰**蹇呴』鏈?`list` 瀛楁**锛屼笉鏄?`songs`锛? +### 3.1 姝g‘鐨勬悳绱㈣繑鍥炴牸寮? ```js return { list: [ { - id: '歌曲ID字符串', - name: '歌曲名', - artists: '歌手1、歌手2', - albumName: '专辑名', - albumId: '专辑ID', + id: '姝屾洸ID瀛楃涓?, + name: '姝屾洸鍚?, + artists: '姝屾墜1銆佹瓕鎵?', + albumName: '涓撹緫鍚?, + albumId: '涓撹緫ID', source: 'tx', // tx/kg/kw/wy/mg/git - pic: '封面大图URL', - mPic: '封面中图URL', - sPic: '封面小图URL', - interval: '3:45', // 播放时长 m:ss + pic: '灏侀潰澶у浘URL', + mPic: '灏侀潰涓浘URL', + sPic: '灏侀潰灏忓浘URL', + interval: '3:45', // 鎾斁鏃堕暱 m:ss qualities: { standard: '3.21MB', exhigh: '7.85MB', @@ -106,29 +95,27 @@ return { } } ], - allPage: 5, // 总页数 - limit: 30, // 每页条数 - total: 150, // 总条数 - source: 'tx' // 平台标识 + allPage: 5, // 鎬婚〉鏁? limit: 30, // 姣忛〉鏉℃暟 + total: 150, // 鎬绘潯鏁? source: 'tx' // 骞冲彴鏍囪瘑 } ``` -### 3.2 字段名对照表 +### 3.2 瀛楁鍚嶅鐓ц〃 -| 含义 | 正确字段名 | 错误字段名 | +| 鍚箟 | 姝g‘瀛楁鍚?| 閿欒瀛楁鍚?| |------|----------|----------| -| 歌手 | `artists` | `artist` | -| 封面图 | `pic` / `mPic` / `sPic` | `picUrl` | -| 时长 | `interval` (字符串 m:ss) | `duration` | -| 歌曲列表 | `list` | `songs` | +| 姝屾墜 | `artists` | `artist` | +| 灏侀潰鍥?| `pic` / `mPic` / `sPic` | `picUrl` | +| 鏃堕暱 | `interval` (瀛楃涓?m:ss) | `duration` | +| 姝屾洸鍒楄〃 | `list` | `songs` | --- -## 四、各平台 API 踩坑记录 +## 鍥涖€佸悇骞冲彴 API 韪╁潙璁板綍 -### 4.1 QQ音乐 (tx) +### 4.1 QQ闊充箰 (tx) -**搜索签名**:`zzcSign` = SHA1 + 自定义索引提取 + XOR 混淆 + base64 +**鎼滅储绛惧悕**锛歚zzcSign` = SHA1 + 鑷畾涔夌储寮曟彁鍙?+ XOR 娣锋穯 + base64 ```js var PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19] @@ -136,41 +123,40 @@ var PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5] var SCRAMBLE_VALUES = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179] ``` -**封面图规则**: -- 有专辑ID:`https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumId}.jpg` -- 无专辑ID(歌手图):`https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` +**灏侀潰鍥捐鍒?*锛?- 鏈変笓杈慖D锛歚https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumId}.jpg` +- 鏃犱笓杈慖D锛堟瓕鎵嬪浘锛夛細`https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` -**getUrl 音质参数**:API 需要带 `k`,如 `320k`,不是 `320` +**getUrl 闊宠川鍙傛暟**锛欰PI 闇€瑕佸甫 `k`锛屽 `320k`锛屼笉鏄?`320` -### 4.2 酷狗音乐 (kg) +### 4.2 閰风嫍闊充箰 (kg) -**搜索接口**:`http://mobilecdn.kugou.com/api/v3/search/song` +**鎼滅储鎺ュ彛**锛歚http://mobilecdn.kugou.com/api/v3/search/song` -**重要**:返回字段是 `errcode`(不是 `error_code`)! +**閲嶈**锛氳繑鍥炲瓧娈垫槸 `errcode`锛堜笉鏄?`error_code`锛夛紒 ```js -// ✅ 正确 +// 鉁?姝g‘ if (result.errcode !== 0) { ... } -// ❌ 错误 +// 鉂?閿欒 if (result.error_code !== 0) { ... } ``` -**封面图**:搜索结果自带 `imgurl` 字段,替换 `{size}` 为 `400` +**灏侀潰鍥?*锛氭悳绱㈢粨鏋滆嚜甯?`imgurl` 瀛楁锛屾浛鎹?`{size}` 涓?`400` ```js var picUrl = item.imgurl ? item.imgurl.replace('{size}', '400') : '' ``` -**getUrl 音质参数**:`128k` / `320k` / `999k` +**getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` -### 4.3 酷我音乐 (kw) +### 4.3 閰锋垜闊充箰 (kw) -**搜索接口**:`http://search.kuwo.cn/r.s` +**鎼滅储鎺ュ彛**锛歚http://search.kuwo.cn/r.s` -**封面图**:`https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` +**灏侀潰鍥?*锛歚https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` -**音质信息**:在 `N_MINFO` 字段中,格式为 `level:xxx,bitrate:xxx,format:xxx,size:xxx;...` +**闊宠川淇℃伅**锛氬湪 `N_MINFO` 瀛楁涓紝鏍煎紡涓?`level:xxx,bitrate:xxx,format:xxx,size:xxx;...` ```js var parts = info.N_MINFO.split(';') @@ -186,35 +172,27 @@ for (var j = 0; j < parts.length; j++) { } ``` -**getUrl 音质参数**:`128k` / `320k` / `999k` +**getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` -### 4.4 网易云音乐 (wy) - -**搜索接口**:`https://music.163.com/api/search/get/web`(简单 GET,不需要 weapi 加密) - -**封面图**:`picId` 需要 Base64 编码后拼接 +### 4.4 缃戞槗浜戦煶涔?(wy) +**鎼滅储鎺ュ彛**锛歚https://music.163.com/api/search/get/web`锛堢畝鍗?GET锛屼笉闇€瑕?weapi 鍔犲瘑锛? +**灏侀潰鍥?*锛歚picId` 闇€瑕?Base64 缂栫爜鍚庢嫾鎺? ```js var picIdStr = String(s.album.picId) var picIdB64 = Buffer.from(picIdStr).toString('base64').replace(/=/g, '') var pic = 'https://p2.music.126.net/' + picIdB64 + '/' + picIdStr + '.jpg' ``` -**getUrl 音质参数**:数字格式 `128000` / `320000` / `999000`(不是带k的) +**getUrl 闊宠川鍙傛暟**锛氭暟瀛楁牸寮?`128000` / `320000` / `999000`锛堜笉鏄甫k鐨勶級 -**加密接口**: -- `eapi`:AES-128-ECB,key = `e82ckenh8dichen8` -- `weapi`:AES-128-CBC + RSA(用于需要登录的接口) +**鍔犲瘑鎺ュ彛**锛?- `eapi`锛欰ES-128-ECB锛宬ey = `e82ckenh8dichen8` +- `weapi`锛欰ES-128-CBC + RSA锛堢敤浜庨渶瑕佺櫥褰曠殑鎺ュ彛锛? +**ext 鍔熻兘**锛?- `userPlaylist`锛氶渶瑕?`playlist_url` 鐜鍙橀噺锛堢綉鏄撲簯涓汉涓婚〉閾炬帴锛?- `dailyRecommend`锛氭瘡鏃ユ帹鑽?- `personalFm`锛氱浜篎M +- `myLikedSongs`锛氭垜鍠滄鐨勯煶涔? +### 4.5 鍜挄闊充箰 (mg) -**ext 功能**: -- `userPlaylist`:需要 `playlist_url` 环境变量(网易云个人主页链接) -- `dailyRecommend`:每日推荐 -- `personalFm`:私人FM -- `myLikedSongs`:我喜欢的音乐 - -### 4.5 咪咕音乐 (mg) - -**搜索签名**:MD5 拼接 +**鎼滅储绛惧悕**锛歁D5 鎷兼帴 ```js var sign = crypto.createHash('md5').update( @@ -222,27 +200,25 @@ var sign = crypto.createHash('md5').update( ).digest('hex') ``` -**封面图**:搜索结果可能返回相对路径,需要拼接域名 - +**灏侀潰鍥?*锛氭悳绱㈢粨鏋滃彲鑳借繑鍥炵浉瀵硅矾寰勶紝闇€瑕佹嫾鎺ュ煙鍚? ```js var img = data.img3 || data.img2 || data.img1 || '' if (img && img.indexOf('http') !== 0) img = 'https://d.musicapp.migu.cn' + img ``` -**getUrl 音质参数**:`128k` / `320k` / `999k` +**getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` -### 4.6 GIT音源 (git) +### 4.6 GIT闊虫簮 (git) -- 无搜索功能(返回空列表) -- 纯音源插件,只有 `getUrl` -- getUrl 音质参数:`128k` / `320k` / `999k` +- 鏃犳悳绱㈠姛鑳斤紙杩斿洖绌哄垪琛級 +- 绾煶婧愭彃浠讹紝鍙湁 `getUrl` +- getUrl 闊宠川鍙傛暟锛歚128k` / `320k` / `999k` --- -## 五、getUrl 测速容灾逻辑 - -所有平台统一使用**并发测速**模式:同时请求多个 API,取第一个成功的结果。 +## 浜斻€乬etUrl 娴嬮€熷鐏鹃€昏緫 +鎵€鏈夊钩鍙扮粺涓€浣跨敤**骞跺彂娴嬮€?*妯″紡锛氬悓鏃惰姹傚涓?API锛屽彇绗竴涓垚鍔熺殑缁撴灉銆? ```js function getUrl(songId, quality) { var apis = buildApis(songId, quality) @@ -272,88 +248,83 @@ function getUrl(songId, quality) { } ``` -### 5.1 音源 API 列表 +### 5.1 闊虫簮 API 鍒楄〃 -| API | 支持平台 | 特点 | +| API | 鏀寔骞冲彴 | 鐗圭偣 | |-----|---------|------| -| 聆澜 | 全部 | 需要 `ceru_key`,最稳定 | -| HUIBQ (lxmusicapi) | 全部 | `X-Request-Key: share-v3` | -| 星海 | 全部 | 聚合接口 | -| 念心 | tx/kg/kw/mg | 个人维护 | -| 长青 | tx/kg/kw/mg | 个人维护 | -| 星海备 | 全部 | 备用 | -| fish | 全部 | 个人维护 | -| HYW | 全部 | 需要 `X-Card-Key` | -| 忆音 | tx | 直接返回 URL | -| 收集QQ | tx | 专用 | -| 收集KW | kw | 专用 | -| bb | wy | 网易云专用 | -| ymc | wy | 网易云专用 | -| unms | wy | 网易云专用 | -| 官方 | wy | 网易云官方 weapi | +| 鑱嗘緶 | 鍏ㄩ儴 | 闇€瑕?`ceru_key`锛屾渶绋冲畾 | +| HUIBQ (lxmusicapi) | 鍏ㄩ儴 | `X-Request-Key: share-v3` | +| 鏄熸捣 | 鍏ㄩ儴 | 鑱氬悎鎺ュ彛 | +| 蹇靛績 | tx/kg/kw/mg | 涓汉缁存姢 | +| 闀块潚 | tx/kg/kw/mg | 涓汉缁存姢 | +| 鏄熸捣澶?| 鍏ㄩ儴 | 澶囩敤 | +| fish | 鍏ㄩ儴 | 涓汉缁存姢 | +| HYW | 鍏ㄩ儴 | 闇€瑕?`X-Card-Key` | +| 蹇嗛煶 | tx | 鐩存帴杩斿洖 URL | +| 鏀堕泦QQ | tx | 涓撶敤 | +| 鏀堕泦KW | kw | 涓撶敤 | +| bb | wy | 缃戞槗浜戜笓鐢?| +| ymc | wy | 缃戞槗浜戜笓鐢?| +| unms | wy | 缃戞槗浜戜笓鐢?| +| 瀹樻柟 | wy | 缃戞槗浜戝畼鏂?weapi | --- -## 六、版本号管理规范 +## 鍏€佺増鏈彿绠$悊瑙勮寖 -**所有平台统一版本号**,每次修改全部升级: +**鎵€鏈夊钩鍙扮粺涓€鐗堟湰鍙?*锛屾瘡娆′慨鏀瑰叏閮ㄥ崌绾э細 -- 当前版本:`0.0.3` -- 下次修改:`0.0.4` -- 再下次:`0.0.5` +- 褰撳墠鐗堟湰锛歚0.0.3` +- 涓嬫淇敼锛歚0.0.1` +- 鍐嶄笅娆★細`0.0.5` -文件名格式:`Koneko_{平台名}_v{版本号}.js` +鏂囦欢鍚嶆牸寮忥細`Koneko_{骞冲彴鍚峿_v{鐗堟湰鍙穧.js` --- -## 七、常见报错与解决 +## 涓冦€佸父瑙佹姤閿欎笌瑙e喅 -| 报错 | 原因 | 解决 | +| 鎶ラ敊 | 鍘熷洜 | 瑙e喅 | |------|------|------| -| `Cannot find module 'axios'` | 用了 axios | 改用 `http`/`https` 内置模块 | -| `String cannot be converted to JSONObject` | 搜索返回了非对象/字符串 | 加 `.catch()` 兜底返回正确格式 | -| `Field 'list' is required` | 搜索返回 `songs` 而非 `list` | 改字段名为 `list` | -| `SyntaxError: Invalid or unexpected token` | 用了 `catch { }` | 改为 `catch (e) { }` | -| `FileNotFoundException: plugin.json` | 12-4版本需要文件夹结构 | 创建 `plugin.json` + `index.js` | -| 搜索无结果 | 字段名不匹配 | 检查 `errcode` vs `error_code` | -| 播放失败 | `mapBr` 返回格式不对 | QQ/kg/kw/mg/git 用 `320k`,wy 用 `320000` | -| 封面图不显示 | URL 格式错误或跨域 | 检查各平台封面图拼接规则 | +| `Cannot find module 'axios'` | 鐢ㄤ簡 axios | 鏀圭敤 `http`/`https` 鍐呯疆妯″潡 | +| `String cannot be converted to JSONObject` | 鎼滅储杩斿洖浜嗛潪瀵硅薄/瀛楃涓?| 鍔?`.catch()` 鍏滃簳杩斿洖姝g‘鏍煎紡 | +| `Field 'list' is required` | 鎼滅储杩斿洖 `songs` 鑰岄潪 `list` | 鏀瑰瓧娈靛悕涓?`list` | +| `SyntaxError: Invalid or unexpected token` | 鐢ㄤ簡 `catch { }` | 鏀逛负 `catch (e) { }` | +| `FileNotFoundException: plugin.json` | 12-4鐗堟湰闇€瑕佹枃浠跺す缁撴瀯 | 鍒涘缓 `plugin.json` + `index.js` | +| 鎼滅储鏃犵粨鏋?| 瀛楁鍚嶄笉鍖归厤 | 妫€鏌?`errcode` vs `error_code` | +| 鎾斁澶辫触 | `mapBr` 杩斿洖鏍煎紡涓嶅 | QQ/kg/kw/mg/git 鐢?`320k`锛寃y 鐢?`320000` | +| 灏侀潰鍥句笉鏄剧ず | URL 鏍煎紡閿欒鎴栬法鍩?| 妫€鏌ュ悇骞冲彴灏侀潰鍥炬嫾鎺ヨ鍒?| --- -## 八、环境变量 - +## 鍏€佺幆澧冨彉閲? ```js var env = global.env || {} -var CERU_KEY = env.ceru_key || '' // 聆澜API密钥 -var WY_COOKIE = env.cookie || '' // 网易云Cookie -var PLAYLIST_URL = env.playlist_url || '' // 网易云个人主页链接 -``` +var CERU_KEY = env.ceru_key || '' // 鑱嗘緶API瀵嗛挜 +var WY_COOKIE = env.cookie || '' // 缃戞槗浜慍ookie +var PLAYLIST_URL = env.playlist_url || '' // 缃戞槗浜戜釜浜轰富椤甸摼鎺?``` -在 QZ Music 设置中配置环境变量,插件通过 `global.env` 读取。 +鍦?QZ Music 璁剧疆涓厤缃幆澧冨彉閲忥紝鎻掍欢閫氳繃 `global.env` 璇诲彇銆? +--- + +## 涔濄€佸畬鏁翠唬鐮佸弬鑰? +6涓钩鍙扮殑瀹屾暣浠g爜瑙佷骇鐗╃洰褰曪細 + +- `Koneko_QQ闊充箰_v0.0.3.js` +- `Koneko_閰风嫍闊充箰_v0.0.3.js` +- `Koneko_閰锋垜闊充箰_v0.0.3.js` +- `Koneko_缃戞槗浜戦煶涔恄v0.0.3.js` +- `Koneko_鍜挄闊充箰_v0.0.3.js` +- `Koneko_GIT闊虫簮_v0.0.3.js` --- -## 九、完整代码参考 - -6个平台的完整代码见产物目录: - -- `Koneko_QQ音乐_v0.0.3.js` -- `Koneko_酷狗音乐_v0.0.3.js` -- `Koneko_酷我音乐_v0.0.3.js` -- `Koneko_网易云音乐_v0.0.3.js` -- `Koneko_咪咕音乐_v0.0.3.js` -- `Koneko_GIT音源_v0.0.3.js` - ---- - -## 十、后续开发建议 - -1. **每次修改全部平台统一升级版本号** -2. **先在浏览器/ curl 测试 API 是否可用** -3. **注意 Javet 兼容性,避免现代 JS 语法** -4. **搜索返回务必包含 `list` 字段** -5. **getUrl 注意各平台音质参数格式差异** -6. **封面图 URL 确保可访问(注意跨域和防盗链)** -7. **所有异步操作加 `.catch()` 兜底** -8. **优先使用官方搜索接口,音源用第三方 API 容灾** +## 鍗併€佸悗缁紑鍙戝缓璁? +1. **姣忔淇敼鍏ㄩ儴骞冲彴缁熶竴鍗囩骇鐗堟湰鍙?* +2. **鍏堝湪娴忚鍣? curl 娴嬭瘯 API 鏄惁鍙敤** +3. **娉ㄦ剰 Javet 鍏煎鎬э紝閬垮厤鐜颁唬 JS 璇硶** +4. **鎼滅储杩斿洖鍔″繀鍖呭惈 `list` 瀛楁** +5. **getUrl 娉ㄦ剰鍚勫钩鍙伴煶璐ㄥ弬鏁版牸寮忓樊寮?* +6. **灏侀潰鍥?URL 纭繚鍙闂紙娉ㄦ剰璺ㄥ煙鍜岄槻鐩楅摼锛?* +7. **鎵€鏈夊紓姝ユ搷浣滃姞 `.catch()` 鍏滃簳** +8. **浼樺厛浣跨敤瀹樻柟鎼滅储鎺ュ彛锛岄煶婧愮敤绗笁鏂?API 瀹圭伨** diff --git a/Koneko_GIT音源_v0.0.4.js b/Koneko_GIT音源_v0.0.1.js similarity index 78% rename from Koneko_GIT音源_v0.0.4.js rename to Koneko_GIT音源_v0.0.1.js index 1ca8fd0..35046cb 100644 --- a/Koneko_GIT音源_v0.0.4.js +++ b/Koneko_GIT音源_v0.0.1.js @@ -1,11 +1,11 @@ -/** - * @name GIT音源 - Koneko - * @description 聚合音源插件: 纯音源(无搜索)+ 多API音源容灾 - * @version 0.0.4 +/** + * @name GIT闊虫簮 - Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 绾煶婧愶紙鏃犳悳绱級+ 澶欰PI闊虫簮瀹圭伨 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 */ 'use strict' @@ -38,7 +38,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=git&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -63,12 +63,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko GIT音源] ' + api.name + ' 成功') + console.log('[Koneko GIT闊虫簮] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko GIT音源] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko GIT闊虫簮] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -80,7 +80,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko GIT音源] 所有API均失败') + console.error('[Koneko GIT闊虫簮] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -112,13 +112,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_git', name: 'GIT音源 - Koneko', version: '0.0.4', description: 'GIT音源聚合插件,聚合2+API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_git', name: 'GIT闊虫簮 - Koneko', version: '0.0.1', description: 'GIT闊虫簮鑱氬悎鎻掍欢锛岃仛鍚?+API闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/Koneko_QQ音乐_v0.0.4.js b/Koneko_QQ音乐_v0.0.1.js similarity index 91% rename from Koneko_QQ音乐_v0.0.4.js rename to Koneko_QQ音乐_v0.0.1.js index 5aee8a7..03f14ab 100644 --- a/Koneko_QQ音乐_v0.0.4.js +++ b/Koneko_QQ音乐_v0.0.1.js @@ -1,11 +1,11 @@ -/** - * @name QQ音乐 - Koneko - * @description 聚合音源插件: 官方搜索 + 多API音源容灾 - * @version 0.0.4 +/** + * @name QQ闊充箰 - Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 瀹樻柟鎼滅储 + 澶欰PI闊虫簮瀹圭伨 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 */ 'use strict' @@ -107,7 +107,7 @@ function formatSingerName(singers) { for (var i = 0; i < singers.length; i++) { if (singers[i].name) names.push(singers[i].name) } - return names.join('、') + return names.join('銆?) } function formatSize(bytes) { @@ -125,7 +125,7 @@ var musicSearch = { musicSearch: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) var data = { comm: { ct: '11', cv: '14090508', v: '14090508', tmeAppID: 'qqmusic', @@ -162,7 +162,7 @@ var musicSearch = { var albumName = '' if (item.album) { albumName = item.album.name; albumId = item.album.mid } var picUrl = '' - if (albumId === '' || albumId === '空') { + if (albumId === '' || albumId === '绌?) { if (item.singer && item.singer.length) { picUrl = 'https://y.gtimg.cn/music/photo_new/T001R500x500M000' + item.singer[0].mid + '.jpg' } @@ -272,7 +272,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=tx&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -286,7 +286,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '忆音', + name: '蹇嗛煶', url: 'https://music.3e0.cn/?server=tencent&type=url&id=' + songId, headers: HEADERS_COMMON, extract: function(res) { @@ -296,7 +296,7 @@ function buildApis(songId, q) { } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=tencent&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -306,7 +306,7 @@ function buildApis(songId, q) { } }, { - name: '收集QQ', + name: '鏀堕泦QQ', url: 'https://cyapi.top/API/qq_music.php?apikey=4d6f7369632d6170692e63656e6775696769692e636f6d&type=json&mid=' + songId, headers: HEADERS_COMMON, extract: function(res) { @@ -316,7 +316,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/tx.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -326,7 +326,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'http://175.27.166.236/kgqq/qq.php?type=mp3&id=' + songId + '&level=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -336,7 +336,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=qq&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -379,12 +379,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko QQ音乐] ' + api.name + ' 成功') + console.log('[Koneko QQ闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko QQ音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko QQ闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -396,7 +396,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko QQ音乐] 所有API均失败') + console.error('[Koneko QQ闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -421,13 +421,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_tx', name: 'QQ音乐 - Koneko', version: '0.0.4', description: 'QQ音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_tx', name: 'QQ闊充箰 - Koneko', version: '0.0.1', description: 'QQ闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/Koneko_咪咕音乐_v0.0.4.js b/Koneko_咪咕音乐_v0.0.1.js similarity index 88% rename from Koneko_咪咕音乐_v0.0.4.js rename to Koneko_咪咕音乐_v0.0.1.js index e3a2e31..bd0b0f4 100644 --- a/Koneko_咪咕音乐_v0.0.4.js +++ b/Koneko_咪咕音乐_v0.0.1.js @@ -1,11 +1,11 @@ -/** - * @name 咪咕音乐 - Koneko - * @description 聚合音源插件: 官方搜索 + 多API音源容灾 - * @version 0.0.4 +/** + * @name 鍜挄闊充箰 - Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 瀹樻柟鎼滅储 + 澶欰PI闊虫簮瀹圭伨 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 */ 'use strict' @@ -70,7 +70,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit var time = Date.now().toString() @@ -120,7 +120,7 @@ var musicSearch = { for (var k = 0; k < data.singerList.length; k++) { if (data.singerList[k].name) names.push(data.singerList[k].name) } - artists = names.join('、') + artists = names.join('銆?) } list.push({ artists: artists, @@ -155,7 +155,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=mg&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -169,7 +169,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=migu&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -179,7 +179,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/mg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -189,7 +189,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://music.haitangw.cc/musicapi/mg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -199,7 +199,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=migu&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -242,12 +242,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 咪咕音乐] ' + api.name + ' 成功') + console.log('[Koneko 鍜挄闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 咪咕音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 鍜挄闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -259,7 +259,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 咪咕音乐] 所有API均失败') + console.error('[Koneko 鍜挄闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -284,13 +284,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_mg', name: '咪咕音乐 - Koneko', version: '0.0.4', description: '咪咕音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_mg', name: '鍜挄闊充箰 - Koneko', version: '0.0.1', description: '鍜挄闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/Koneko_插件开发文档_v0.0.1.md b/Koneko_插件开发文档_v0.0.1.md new file mode 100644 index 0000000..e739cfe --- /dev/null +++ b/Koneko_插件开发文档_v0.0.1.md @@ -0,0 +1,221 @@ +# Koneko QZ Music v2/v3 鎻掍欢寮€鍙戞枃妗? +> 鐗堟湰锛?.0.4 | 浣滆€咃細浜戞眬(Miao-moe) | 鏁寸悊鏃ユ湡锛?026-06-20 + +--- + +## 涓€銆佹杩? +涓?QZ Music v2/v3 缂栧啓 6 涓煶涔愬钩鍙版嫇灞曟彃浠讹細 +- QQ闊充箰 (`koneko_tx`) +- 閰风嫍闊充箰 (`koneko_kg`) +- 閰锋垜闊充箰 (`koneko_kw`) +- 缃戞槗浜戦煶涔?(`koneko_wy`) +- 鍜挄闊充箰 (`koneko_mg`) +- GIT闊虫簮 (`koneko_git`) + +## 浜屻€佹彃浠惰鑼? +### 2.1 杩愯鐜 + +- Node.js 杩愯鏃讹紙Javet/V8锛?- CommonJS 妯″潡瑙勮寖 +- `module.exports` 瀵煎嚭鎺ュ彛 +- 涓嶆敮鎸?`axios`锛屼娇鐢ㄥ唴缃?`http`/`https` 妯″潡 + +### 2.2 ES5 鍏煎锛圝avet/V8 闄愬埗锛? +| 璇硶 | 鐘舵€?| 鏇夸唬 | +|------|------|------| +| `let` / `const` | 鉂?| `var` | +| 绠ご鍑芥暟 | 鉂?| `function() {}` | +| `async`/`await` | 鉂?| Promise 閾惧紡 | +| `catch { }` 鏃犲弬鏁?| 鉂?| `catch (e) { }` | +| `Promise.allSettled` | 鉂?| `Promise.all` + 鎵嬪姩鍖呰 | +| `Object.entries/values` | 鉂?| `for...in` | +| `Array.includes` | 鉂?| `indexOf` | +| `String.startsWith` | 鉂?| `indexOf(...) === 0` | +| `class` | 鉂?| 瀵硅薄瀛楅潰閲?| +| 妯℃澘瀛楃涓?`${}` | 鉁?| - | +| `Buffer` | 鉁?| - | + +### 2.3 鎻掍欢瀵煎嚭鏍煎紡 + +```js +module.exports = { + musicSearch: { search: fn, tipSearch: fn, hotSearch: fn }, + tipSearch: { getList: fn }, + hotSearch: { getList: fn }, + getUrl: fn, + getLyric: fn, + songList: { getListDetail: fn }, + album: { getListDetail: fn }, + pluginInfo: { info, env, ext, quality, supportFunc }, + // 缃戞槗浜戠壒鏈? userPlaylist: fn, + dailyRecommend: fn, + personalFm: fn, + myLikedSongs: fn +} +``` + +### 2.4 鎼滅储缁撴灉鏍煎紡 + +```js +{ + list: [{ + id: String, + name: String, + artists: String, // 鐢?"銆? 鍒嗛殧 + source: String, // tx/kg/kw/wy/mg + pic: String, // 灏侀潰澶у浘 + mPic: String, // 灏侀潰涓浘 + sPic: String, // 灏侀潰灏忓浘 + albumName: String, + albumId: String, + interval: String, // "m:ss" + qualities: { standard: '3.21MB', exhigh: '7.85MB', ... } + }], + allPage: Number, + limit: Number, + total: Number, + source: String +} +``` + +### 2.5 鐜鍙橀噺 + +閫氳繃 `global.env` 璇诲彇锛? +```js +var env = global.env || {} +var CERU_KEY = env.ceru_key || '' +var WY_COOKIE = env.cookie || '' +var PLAYLIST_URL = env.playlist_url || '' +``` + +### 2.6 闊宠川鏍囪瘑 + +| ID | 鍚箟 | +|----|------| +| `standard` | 鏍囧噯闊宠川 (128k) | +| `exhigh` | 楂樺搧闊宠川 (320k) | +| `lossless` | 鏃犳崯闊宠川 (FLAC) | +| `hires` | Hi-Res | +| `jyeffect` | 楂樻竻鐜粫澹?| +| `sky` | 娌夋蹈鐜粫澹?| +| `jymaster` | 瓒呮竻姣嶅甫 | + +## 涓夈€佸悇骞冲彴 API + +### 3.1 QQ闊充箰 (tx) + +**鎼滅储绛惧悕**: `zzcSign` = SHA1 + 鑷畾涔夌储寮曟彁鍙?+ XOR 娣锋穯 + base64 + +```js +var PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19] +var PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5] +var SCRAMBLE_VALUES = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179] +``` + +**灏侀潰鍥捐鍒?*锛?- 鏈変笓杈慖D锛歚https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumId}.jpg` +- 鏃犱笓杈慖D锛歚https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` + +**getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` + +### 3.2 閰风嫍闊充箰 (kg) + +**鎼滅储鎺ュ彛**锛歚http://mobilecdn.kugou.com/api/v3/search/song` + +**娉ㄦ剰**锛氳繑鍥炲瓧娈垫槸 `errcode`锛堜笉鏄?`error_code`锛? +**灏侀潰鍥?*锛氭悳绱㈢粨鏋滆嚜甯?`imgurl`锛屾浛鎹?`{size}` 涓?`400` + +### 3.3 閰锋垜闊充箰 (kw) + +**鎼滅储鎺ュ彛**锛歚http://search.kuwo.cn/r.s` + +**灏侀潰鍥?*锛歚https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` + +**闊宠川淇℃伅**锛氬湪 `N_MINFO` 瀛楁涓В鏋? +### 3.4 缃戞槗浜戦煶涔?(wy) + +**鎼滅储鎺ュ彛**锛歚https://music.163.com/api/search/get/web`锛圙ET锛屼笉闇€瑕?weapi锛? +**灏侀潰鍥?*锛歚picId` 闇€ Base64 缂栫爜 + +```js +var picIdB64 = Buffer.from(String(s.album.picId)).toString('base64').replace(/=/g, '') +var pic = 'https://p2.music.126.net/' + picIdB64 + '/' + picIdStr + '.jpg' +``` + +**鍔犲瘑鎺ュ彛**锛?- `eapi`锛欰ES-128-ECB锛宬ey = `e82ckenh8dichen8` +- `weapi`锛欰ES-128-CBC + RSA + +**Cookie 鍔熻兘**锛堥渶璁剧疆 `cookie` 鐜鍙橀噺锛夛細 +- `userPlaylist()` - 涓汉姝屽崟锛堥渶 `playlist_url`锛?- `dailyRecommend()` - 姣忔棩鎺ㄨ崘 +- `personalFm()` - 绉佷汉FM +- `myLikedSongs()` - 鎴戝枩娆㈢殑闊充箰 +- `songList(id)` - 姝屽崟璇︽儏 +- `album(id)` - 涓撹緫璇︽儏 +- `getLyric(id)` - 姝岃瘝鑾峰彇 + +### 3.5 鍜挄闊充箰 (mg) + +**鎼滅储绛惧悕**锛歁D5 鎷兼帴 + +```js +var sign = crypto.createHash('md5').update( + str + signatureMd5 + 'yyapp2d16148780a1dcc7408e06336b98cfd50' + deviceId + time +).digest('hex') +``` + +**灏侀潰鍥?*锛氭悳绱㈢粨鏋滃彲鑳借繑鍥炵浉瀵硅矾寰勶紝闇€鎷兼帴 `https://d.musicapp.migu.cn` + +### 3.6 GIT闊虫簮 (git) + +绾煶婧愭彃浠讹紝鏃犳悳绱㈠姛鑳斤紝浠?`getUrl` + +## 鍥涖€乬etUrl 瀹圭伨鏈哄埗 + +鎵€鏈夊钩鍙扮粺涓€浣跨敤**骞跺彂娴嬮€?*妯″紡锛? +```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.url + } + return '' +}) +``` + +### 闊虫簮 API 鍒楄〃 + +| API | 骞冲彴 | 璇存槑 | +|-----|------|------| +| 鑱嗘緶 | 鍏ㄩ儴 | 闇€ `ceru_key`锛屾渶绋冲畾 | +| HUIBQ | 鍏ㄩ儴 | `X-Request-Key: share-v3` | +| 鏄熸捣 | 鍏ㄩ儴 | 鑱氬悎鎺ュ彛 | +| 蹇靛績 | tx/kg/kw/mg | 涓汉缁存姢 | +| 闀块潚 | tx/kg/kw/mg | 涓汉缁存姢 | +| 鏄熸捣澶?| 鍏ㄩ儴 | 澶囩敤 | +| fish | 鍏ㄩ儴 | 涓汉缁存姢 | +| HYW | 鍏ㄩ儴 | 闇€ `X-Card-Key` | +| 蹇嗛煶 | tx | 鐩存帴杩斿洖 URL | +| 鏀堕泦QQ | tx | QQ涓撶敤 | +| 鏀堕泦KW | kw | 閰锋垜涓撶敤 | +| bb | wy | 缃戞槗浜戜笓鐢?| +| ymc | wy | 缃戞槗浜戜笓鐢?| +| unms | wy | 缃戞槗浜戜笓鐢?| +| 瀹樻柟 weapi | wy | 缃戞槗浜戝畼鏂?| + +## 浜斻€佺増鏈鐞? +- 鎵€鏈夊钩鍙扮粺涓€鐗堟湰鍙?- 褰撳墠鐗堟湰锛歚0.0.1` +- 鏂囦欢鍚嶆牸寮忥細`Koneko_{骞冲彴鍚峿_v{鐗堟湰鍙穧.js` + +## 鍏€佸父瑙侀棶棰? +| 闂 | 鍘熷洜 | 瑙e喅 | +|------|------|------| +| `Cannot find module 'axios'` | 鐢ㄤ簡 axios | 鏀圭敤鍐呯疆 `http`/`https` | +| `Field 'list' is required` | 杩斿洖 `songs` 鑰岄潪 `list` | 鏀瑰瓧娈靛悕涓?`list` | +| 鎼滅储鏃犵粨鏋?| 瀛楁鍚嶄笉鍖归厤 | 妫€鏌?`errcode` vs `error_code` | +| 鎾斁澶辫触 | `mapBr` 杩斿洖鏍煎紡涓嶅 | tx/kg/kw/mg/git 鐢?`320k`锛寃y 鐢?`320000` | +| 灏侀潰鍥句笉鏄剧ず | URL 鏍煎紡閿欒 | 妫€鏌ュ悇骞冲彴鎷兼帴瑙勫垯 | + +## 涓冦€佺浉鍏抽摼鎺? +- Gitea: http://171.80.3.149:4321/miao-moe +- CeruMusic: http://171.80.3.149:4321/miao-moe/CeruMusic +- QZMusic PC: http://171.80.3.149:4321/miao-moe/QZMusic_PC diff --git a/Koneko_插件开发文档_v0.0.4.md b/Koneko_插件开发文档_v0.0.4.md deleted file mode 100644 index 215ff7b..0000000 --- a/Koneko_插件开发文档_v0.0.4.md +++ /dev/null @@ -1,239 +0,0 @@ -# Koneko QZ Music v2/v3 插件开发文档 - -> 版本:0.0.4 | 作者:云汀(Miao-moe) | 整理日期:2026-06-20 - ---- - -## 一、概述 - -为 QZ Music v2/v3 编写 6 个音乐平台拓展插件: -- QQ音乐 (`koneko_tx`) -- 酷狗音乐 (`koneko_kg`) -- 酷我音乐 (`koneko_kw`) -- 网易云音乐 (`koneko_wy`) -- 咪咕音乐 (`koneko_mg`) -- GIT音源 (`koneko_git`) - -## 二、插件规范 - -### 2.1 运行环境 - -- Node.js 运行时(Javet/V8) -- CommonJS 模块规范 -- `module.exports` 导出接口 -- 不支持 `axios`,使用内置 `http`/`https` 模块 - -### 2.2 ES5 兼容(Javet/V8 限制) - -| 语法 | 状态 | 替代 | -|------|------|------| -| `let` / `const` | ❌ | `var` | -| 箭头函数 | ❌ | `function() {}` | -| `async`/`await` | ❌ | Promise 链式 | -| `catch { }` 无参数 | ❌ | `catch (e) { }` | -| `Promise.allSettled` | ❌ | `Promise.all` + 手动包装 | -| `Object.entries/values` | ❌ | `for...in` | -| `Array.includes` | ❌ | `indexOf` | -| `String.startsWith` | ❌ | `indexOf(...) === 0` | -| `class` | ❌ | 对象字面量 | -| 模板字符串 `${}` | ✅ | - | -| `Buffer` | ✅ | - | - -### 2.3 插件导出格式 - -```js -module.exports = { - musicSearch: { search: fn, tipSearch: fn, hotSearch: fn }, - tipSearch: { getList: fn }, - hotSearch: { getList: fn }, - getUrl: fn, - getLyric: fn, - songList: { getListDetail: fn }, - album: { getListDetail: fn }, - pluginInfo: { info, env, ext, quality, supportFunc }, - // 网易云特有 - userPlaylist: fn, - dailyRecommend: fn, - personalFm: fn, - myLikedSongs: fn -} -``` - -### 2.4 搜索结果格式 - -```js -{ - list: [{ - id: String, - name: String, - artists: String, // 用 "、" 分隔 - source: String, // tx/kg/kw/wy/mg - pic: String, // 封面大图 - mPic: String, // 封面中图 - sPic: String, // 封面小图 - albumName: String, - albumId: String, - interval: String, // "m:ss" - qualities: { standard: '3.21MB', exhigh: '7.85MB', ... } - }], - allPage: Number, - limit: Number, - total: Number, - source: String -} -``` - -### 2.5 环境变量 - -通过 `global.env` 读取: - -```js -var env = global.env || {} -var CERU_KEY = env.ceru_key || '' -var WY_COOKIE = env.cookie || '' -var PLAYLIST_URL = env.playlist_url || '' -``` - -### 2.6 音质标识 - -| ID | 含义 | -|----|------| -| `standard` | 标准音质 (128k) | -| `exhigh` | 高品音质 (320k) | -| `lossless` | 无损音质 (FLAC) | -| `hires` | Hi-Res | -| `jyeffect` | 高清环绕声 | -| `sky` | 沉浸环绕声 | -| `jymaster` | 超清母带 | - -## 三、各平台 API - -### 3.1 QQ音乐 (tx) - -**搜索签名**: `zzcSign` = SHA1 + 自定义索引提取 + XOR 混淆 + base64 - -```js -var PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19] -var PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5] -var SCRAMBLE_VALUES = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179] -``` - -**封面图规则**: -- 有专辑ID:`https://y.gtimg.cn/music/photo_new/T002R500x500M000{albumId}.jpg` -- 无专辑ID:`https://y.gtimg.cn/music/photo_new/T001R500x500M000{singerMid}.jpg` - -**getUrl 音质参数**:`128k` / `320k` / `999k` - -### 3.2 酷狗音乐 (kg) - -**搜索接口**:`http://mobilecdn.kugou.com/api/v3/search/song` - -**注意**:返回字段是 `errcode`(不是 `error_code`) - -**封面图**:搜索结果自带 `imgurl`,替换 `{size}` 为 `400` - -### 3.3 酷我音乐 (kw) - -**搜索接口**:`http://search.kuwo.cn/r.s` - -**封面图**:`https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` - -**音质信息**:在 `N_MINFO` 字段中解析 - -### 3.4 网易云音乐 (wy) - -**搜索接口**:`https://music.163.com/api/search/get/web`(GET,不需要 weapi) - -**封面图**:`picId` 需 Base64 编码 - -```js -var picIdB64 = Buffer.from(String(s.album.picId)).toString('base64').replace(/=/g, '') -var pic = 'https://p2.music.126.net/' + picIdB64 + '/' + picIdStr + '.jpg' -``` - -**加密接口**: -- `eapi`:AES-128-ECB,key = `e82ckenh8dichen8` -- `weapi`:AES-128-CBC + RSA - -**Cookie 功能**(需设置 `cookie` 环境变量): -- `userPlaylist()` - 个人歌单(需 `playlist_url`) -- `dailyRecommend()` - 每日推荐 -- `personalFm()` - 私人FM -- `myLikedSongs()` - 我喜欢的音乐 -- `songList(id)` - 歌单详情 -- `album(id)` - 专辑详情 -- `getLyric(id)` - 歌词获取 - -### 3.5 咪咕音乐 (mg) - -**搜索签名**:MD5 拼接 - -```js -var sign = crypto.createHash('md5').update( - str + signatureMd5 + 'yyapp2d16148780a1dcc7408e06336b98cfd50' + deviceId + time -).digest('hex') -``` - -**封面图**:搜索结果可能返回相对路径,需拼接 `https://d.musicapp.migu.cn` - -### 3.6 GIT音源 (git) - -纯音源插件,无搜索功能,仅 `getUrl` - -## 四、getUrl 容灾机制 - -所有平台统一使用**并发测速**模式: - -```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.url - } - return '' -}) -``` - -### 音源 API 列表 - -| API | 平台 | 说明 | -|-----|------|------| -| 聆澜 | 全部 | 需 `ceru_key`,最稳定 | -| HUIBQ | 全部 | `X-Request-Key: share-v3` | -| 星海 | 全部 | 聚合接口 | -| 念心 | tx/kg/kw/mg | 个人维护 | -| 长青 | tx/kg/kw/mg | 个人维护 | -| 星海备 | 全部 | 备用 | -| fish | 全部 | 个人维护 | -| HYW | 全部 | 需 `X-Card-Key` | -| 忆音 | tx | 直接返回 URL | -| 收集QQ | tx | QQ专用 | -| 收集KW | kw | 酷我专用 | -| bb | wy | 网易云专用 | -| ymc | wy | 网易云专用 | -| unms | wy | 网易云专用 | -| 官方 weapi | wy | 网易云官方 | - -## 五、版本管理 - -- 所有平台统一版本号 -- 当前版本:`0.0.4` -- 文件名格式:`Koneko_{平台名}_v{版本号}.js` - -## 六、常见问题 - -| 问题 | 原因 | 解决 | -|------|------|------| -| `Cannot find module 'axios'` | 用了 axios | 改用内置 `http`/`https` | -| `Field 'list' is required` | 返回 `songs` 而非 `list` | 改字段名为 `list` | -| 搜索无结果 | 字段名不匹配 | 检查 `errcode` vs `error_code` | -| 播放失败 | `mapBr` 返回格式不对 | tx/kg/kw/mg/git 用 `320k`,wy 用 `320000` | -| 封面图不显示 | URL 格式错误 | 检查各平台拼接规则 | - -## 七、相关链接 - -- Gitea: http://171.80.3.149:4321/miao-moe -- CeruMusic: http://171.80.3.149:4321/miao-moe/CeruMusic -- QZMusic PC: http://171.80.3.149:4321/miao-moe/QZMusic_PC diff --git a/Koneko_网易云音乐_v0.0.4.js b/Koneko_网易云音乐_v0.0.1.js similarity index 89% rename from Koneko_网易云音乐_v0.0.4.js rename to Koneko_网易云音乐_v0.0.1.js index ec6b056..d950e8d 100644 --- a/Koneko_网易云音乐_v0.0.4.js +++ b/Koneko_网易云音乐_v0.0.1.js @@ -1,13 +1,12 @@ -/** - * @name 网易云音乐 - Koneko - * @description 聚合音源插件: 官方搜索 + 多API音源容灾 + 完整Cookie功能 - * @version 0.0.4 +/** + * @name 缃戞槗浜戦煶涔?- Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 瀹樻柟鎼滅储 + 澶欰PI闊虫簮瀹圭伨 + 瀹屾暣Cookie鍔熻兘 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) - * cookie - 网易云Cookie,用于搜索增强、每日推荐、私人FM、我喜欢的音乐 - * playlist_url - 网易云个人主页链接,用于获取个人歌单 + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 + * cookie - 缃戞槗浜慍ookie锛岀敤浜庢悳绱㈠寮恒€佹瘡鏃ユ帹鑽愩€佺浜篎M銆佹垜鍠滄鐨勯煶涔? * playlist_url - 缃戞槗浜戜釜浜轰富椤甸摼鎺ワ紝鐢ㄤ簬鑾峰彇涓汉姝屽崟 */ 'use strict' @@ -197,7 +196,7 @@ function wySearch(keyword, page, limit) { list.push({ id: s.id ? String(s.id) : '', name: s.name || '', - artists: artists.join('、'), + artists: artists.join('銆?), albumName: album, albumId: albumId, source: 'wy', @@ -227,7 +226,7 @@ function wyTipSearch(keyword) { var tips = [] for (var i = 0; i < res.result.songs.length; i++) { var s = res.result.songs[i] - tips.push(s.name + ' - ' + (s.artists && s.artists[0] ? s.artists[0].name : '未知')) + tips.push(s.name + ' - ' + (s.artists && s.artists[0] ? s.artists[0].name : '鏈煡')) } return tips }).catch(function(e) { return [] }) @@ -480,7 +479,7 @@ var songList = { list.push({ id: String(s.id), name: s.name || '', - artists: artists.join('、'), + artists: artists.join('銆?), source: 'wy', pic: s.al ? s.al.picUrl : '', mPic: s.al ? s.al.picUrl : '', @@ -530,7 +529,7 @@ var album = { list.push({ id: String(s.id), name: s.name || '', - artists: artists.join('、'), + artists: artists.join('銆?), source: 'wy', pic: res.album.picUrl || '', mPic: res.album.picUrl || '', @@ -604,26 +603,26 @@ var hotSearch = { } var pluginInfo = { - info: { id: 'koneko_wy', name: '网易云音乐 - Koneko', version: '0.0.4', description: '网易云音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换,支持Cookie功能' }, + info: { id: 'koneko_wy', name: '缃戞槗浜戦煶涔?- Koneko', version: '0.0.1', description: '缃戞槗浜戦煶涔愯仛鍚堥煶婧愭彃浠讹紝瀹樻柟鎼滅储+澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹紝鏀寔Cookie鍔熻兘' }, env: [ - { key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }, - { key: 'playlist_url', name: '个人主页链接', description: '网易云音乐个人主页链接,用于获取个人歌单' }, - { key: 'cookie', name: 'Cookie', description: '网易云音乐Cookie,用于每日推荐/私人FM/我喜欢的音乐/歌单/专辑/歌词等' } + { key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }, + { key: 'playlist_url', name: '涓汉涓婚〉閾炬帴', description: '缃戞槗浜戦煶涔愪釜浜轰富椤甸摼鎺ワ紝鐢ㄤ簬鑾峰彇涓汉姝屽崟' }, + { key: 'cookie', name: 'Cookie', description: '缃戞槗浜戦煶涔怌ookie锛岀敤浜庢瘡鏃ユ帹鑽?绉佷汉FM/鎴戝枩娆㈢殑闊充箰/姝屽崟/涓撹緫/姝岃瘝绛? } ], ext: [ - { name: '个人歌单', description: '通过分享链接获取个人歌单', entry: 'plugin.userPlaylist()', type: 'playlists' }, - { name: '每日推荐', description: '获取每日推荐歌曲', entry: 'plugin.dailyRecommend()', type: 'songs' }, - { name: '私人FM', description: '获取私人FM歌曲', entry: 'plugin.personalFm()', type: 'songs' }, - { name: '我喜欢的音乐', description: '获取我喜欢的音乐列表', entry: 'plugin.myLikedSongs()', type: 'songs' } + { name: '涓汉姝屽崟', description: '閫氳繃鍒嗕韩閾炬帴鑾峰彇涓汉姝屽崟', entry: 'plugin.userPlaylist()', type: 'playlists' }, + { name: '姣忔棩鎺ㄨ崘', description: '鑾峰彇姣忔棩鎺ㄨ崘姝屾洸', entry: 'plugin.dailyRecommend()', type: 'songs' }, + { name: '绉佷汉FM', description: '鑾峰彇绉佷汉FM姝屾洸', entry: 'plugin.personalFm()', type: 'songs' }, + { name: '鎴戝枩娆㈢殑闊充箰', description: '鑾峰彇鎴戝枩娆㈢殑闊充箰鍒楄〃', entry: 'plugin.myLikedSongs()', type: 'songs' } ], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' }, - { name: '高清环绕声', ui: 'DB', id: 'jyeffect' }, - { name: '沉浸环绕声', ui: 'SK', id: 'sky' }, - { name: '超清母带', ui: 'MT', id: 'jymaster' } + { name: '楂樻竻鐜粫澹?, ui: 'DB', id: 'jyeffect' }, + { name: '娌夋蹈鐜粫澹?, ui: 'SK', id: 'sky' }, + { name: '瓒呮竻姣嶅甫', ui: 'MT', id: 'jymaster' } ], supportFunc: [] } diff --git a/Koneko_酷我音乐_v0.0.4.js b/Koneko_酷我音乐_v0.0.1.js similarity index 89% rename from Koneko_酷我音乐_v0.0.4.js rename to Koneko_酷我音乐_v0.0.1.js index 44648a4..1960943 100644 --- a/Koneko_酷我音乐_v0.0.4.js +++ b/Koneko_酷我音乐_v0.0.1.js @@ -1,11 +1,11 @@ -/** - * @name 酷我音乐 - Koneko - * @description 聚合音源插件: 官方搜索 + 多API音源容灾 - * @version 0.0.4 +/** + * @name 閰锋垜闊充箰 - Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 瀹樻柟鎼滅储 + 澶欰PI闊虫簮瀹圭伨 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 */ 'use strict' @@ -63,7 +63,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit return httpGet( @@ -110,7 +110,7 @@ var musicSearch = { picUrl = 'http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=MUSIC_' + songId } var artistStr = safeGet(info, ['ARTIST', 'artist', 'singer', 'author']) - if (artistStr) artistStr = String(artistStr).replace(/&/g, '、') + if (artistStr) artistStr = String(artistStr).replace(/&/g, '銆?) var duration = parseInt(safeGet(info, ['DURATION', 'duration', 'Duration', 'time'])) || 0 var songName = safeGet(info, ['SONGNAME', 'songname', 'name', 'Name', 'title']) var albumName = safeGet(info, ['ALBUM', 'album', 'Album', 'album_name']) @@ -181,7 +181,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=kw&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -195,7 +195,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '官方反代', + name: '瀹樻柟鍙嶄唬', url: 'http://antiserver.kuwo.cn/anti.s?rid=' + fullRid + '&format=mp3&type=convert_url', headers: HEADERS_COMMON, extract: function(res) { @@ -204,7 +204,7 @@ function buildApis(songId, q) { } }, { - name: '官方反代2', + name: '瀹樻柟鍙嶄唬2', url: 'http://antiserver.kuwo.cn/anti.s?type=convert_url&rid=' + fullRid + '&format=aac|mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -213,7 +213,7 @@ function buildApis(songId, q) { } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=kuwo&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -223,7 +223,7 @@ function buildApis(songId, q) { } }, { - name: '收集KW', + name: '鏀堕泦KW', url: 'https://kw-api.cenguigui.cn/api/song/url?id=' + songId + '&quality=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -233,7 +233,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/kw.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -243,7 +243,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://musicapi.haitangw.net/music/kw.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -253,7 +253,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=kuwo&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -296,12 +296,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 酷我音乐] ' + api.name + ' 成功') + console.log('[Koneko 閰锋垜闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 酷我音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 閰锋垜闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -313,7 +313,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 酷我音乐] 所有API均失败') + console.error('[Koneko 閰锋垜闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -351,13 +351,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_kw', name: '酷我音乐 - Koneko', version: '0.0.4', description: '酷我音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_kw', name: '閰锋垜闊充箰 - Koneko', version: '0.0.1', description: '閰锋垜闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/Koneko_酷狗音乐_v0.0.4.js b/Koneko_酷狗音乐_v0.0.1.js similarity index 87% rename from Koneko_酷狗音乐_v0.0.4.js rename to Koneko_酷狗音乐_v0.0.1.js index 537e783..5abe9ef 100644 --- a/Koneko_酷狗音乐_v0.0.4.js +++ b/Koneko_酷狗音乐_v0.0.1.js @@ -1,11 +1,11 @@ -/** - * @name 酷狗音乐 - Koneko - * @description 聚合音源插件: 官方搜索 + 多API音源容灾 - * @version 0.0.4 +/** + * @name 閰风嫍闊充箰 - Koneko + * @description 鑱氬悎闊虫簮鎻掍欢: 瀹樻柟鎼滅储 + 澶欰PI闊虫簮瀹圭伨 + * @version 0.0.1 * @author Miao-moe * - * 环境变量: - * ceru_key - 聆澜API密钥(可选) + * 鐜鍙橀噺: + * ceru_key - 鑱嗘緶API瀵嗛挜锛堝彲閫夛級 */ 'use strict' @@ -55,7 +55,7 @@ var musicSearch = { search: function(str, page, limit, retryNum) { var self = this if (retryNum === undefined) retryNum = 0 - if (++retryNum > 3) return Promise.reject(new Error('搜索失败')) + if (++retryNum > 3) return Promise.reject(new Error('鎼滅储澶辫触')) if (!page) page = 1 if (limit == null) limit = this.limit return httpGet( @@ -147,7 +147,7 @@ function buildApis(songId, q) { var apis = [] if (CERU_KEY) { apis.push({ - name: '聆澜', + name: '鑱嗘緶', url: 'https://source.shiqianjiang.cn/api/music/url?source=kg&songId=' + songId + '&quality=' + q, headers: { 'User-Agent': HEADERS_COMMON['User-Agent'], 'X-API-Key': CERU_KEY }, extract: function(res) { return res && res.code === 200 && res.url ? res.url : null } @@ -161,7 +161,7 @@ function buildApis(songId, q) { extract: function(res) { return res && res.code === 0 && res.url ? res.url : null } }, { - name: '星海', + name: '鏄熸捣', url: 'https://music-api.gdstudio.xyz/api.php?types=url&source=kugou&id=' + songId + '&br=' + br, headers: HEADERS_COMMON, extract: function(res) { @@ -171,7 +171,7 @@ function buildApis(songId, q) { } }, { - name: '念心', + name: '蹇靛績', url: 'https://music.nxinxz.com/kgqq/kg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -181,7 +181,7 @@ function buildApis(songId, q) { } }, { - name: '长青', + name: '闀块潚', url: 'https://music.haitangw.cc/kgqq/kg.php?id=' + songId + '&level=' + q + '&type=mp3', headers: HEADERS_COMMON, extract: function(res) { @@ -191,7 +191,7 @@ function buildApis(songId, q) { } }, { - name: '星海备', + name: '鏄熸捣澶?, url: 'https://music-dl.sayqz.com/api/?source=kugou&id=' + songId + '&type=url&br=' + q, headers: HEADERS_COMMON, extract: function(res) { @@ -234,12 +234,12 @@ function getUrl(songId, quality) { httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) { - console.log('[Koneko 酷狗音乐] ' + api.name + ' 成功') + console.log('[Koneko 閰风嫍闊充箰] ' + api.name + ' 鎴愬姛') return { name: api.name, url: url } } - throw new Error(api.name + ' 无有效URL') + throw new Error(api.name + ' 鏃犳湁鏁圲RL') }).catch(function(err) { - console.error('[Koneko 酷狗音乐] ' + api.name + ' 失败: ' + err.message) + console.error('[Koneko 閰风嫍闊充箰] ' + api.name + ' 澶辫触: ' + err.message) throw err }) ) @@ -251,7 +251,7 @@ function getUrl(songId, quality) { for (var i = 0; i < results.length; i++) { if (results[i].status === 'fulfilled') return results[i].value.url } - console.error('[Koneko 酷狗音乐] 所有API均失败') + console.error('[Koneko 閰风嫍闊充箰] 鎵€鏈堿PI鍧囧け璐?) return '' }) } @@ -276,13 +276,13 @@ function musicDetail(id) { return Promise.resolve(null) } function musicInfo(id) { return Promise.resolve(null) } var pluginInfo = { - info: { id: 'koneko_kg', name: '酷狗音乐 - Koneko', version: '0.0.4', description: '酷狗音乐聚合音源插件,官方搜索+多API音源,自动测速容灾切换' }, - env: [{ key: 'ceru_key', name: '聆澜API Key', description: '聆澜音源API密钥,留空则跳过聆澜音源' }], + info: { id: 'koneko_kg', name: '閰风嫍闊充箰 - Koneko', version: '0.0.1', description: '閰风嫍闊充箰鑱氬悎闊虫簮鎻掍欢锛屽畼鏂规悳绱?澶欰PI闊虫簮锛岃嚜鍔ㄦ祴閫熷鐏惧垏鎹? }, + env: [{ key: 'ceru_key', name: '鑱嗘緶API Key', description: '鑱嗘緶闊虫簮API瀵嗛挜锛岀暀绌哄垯璺宠繃鑱嗘緶闊虫簮' }], ext: [], quality: [ - { name: '标准音质', ui: '标', id: 'standard' }, - { name: '高品音质', ui: 'HQ', id: 'exhigh' }, - { name: '无损音质', ui: 'SQ', id: 'lossless' }, + { name: '鏍囧噯闊宠川', ui: '鏍?, id: 'standard' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: 'exhigh' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'lossless' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], supportFunc: [] diff --git a/Koneko插件开发避坑指南_v0.0.1.md b/Koneko插件开发避坑指南_v0.0.1.md new file mode 100644 index 0000000..63ea755 --- /dev/null +++ b/Koneko插件开发避坑指南_v0.0.1.md @@ -0,0 +1,123 @@ +# Koneko QZ Music v2/v3 鎻掍欢寮€鍙戦伩鍧戞寚鍗? +> 鐗堟湰: 0.0.1 | 浣滆€? 浜戞眬(Miao-moe) | 鐩爣: 鏀寔杩佺Щ鍒板叾浠?AI 缁х画寮€鍙? +--- + +## 涓€銆侀」鐩儗鏅? +QZ Music v2/v3 鏄竴娆?Android/PC 闊充箰鎾斁鍣紝鏀寔閫氳繃**鎷撳睍鎻掍欢**鎺ュ叆澶氬钩鍙伴煶婧愩€傛彃浠剁郴缁熷熀浜?Node.js 杩愯鏃讹紙Javet/V8锛夛紝姣忎釜鎻掍欢鏄竴涓崟鐙殑 `.js` 鏂囦欢锛岄€氳繃 `module.exports` 瀵煎嚭鎺ュ彛銆? +### 鎻掍欢鍔犺浇鏈哄埗 + +- 杩愯鏃剁幆澧冨彉閲忛€氳繃 `global.env` 璁块棶锛?*涓嶆槸** `process.env` +- 鎻掍欢涓哄崟 `.js` 鏂囦欢鏍煎紡 + +## 浜屻€丣avet/V8 鍏煎鎬уぇ鍧戯紙鏈€閲嶈锛? +QZ Music 浣跨敤 Javet 浣滀负 JS 杩愯鏃讹紙鍩轰簬 V8锛夛紝**涓嶆敮鎸佺幇浠?ES 璇硶**锛? +| 璇硶 | 鐘舵€?| 姝g‘鍐欐硶 | +|------|------|---------| +| `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 +// 鉁?姝g‘ +return { list: [...], allPage: N, limit: N, total: N, source: 'tx' } + +// 鉂?閿欒 +return { songs: [...], total: N } +``` + +### 瀛楁鍚嶅鐓? +| 鍚箟 | 姝g‘瀛楁 | 閿欒瀛楁 | +|------|---------|---------| +| 姝屾墜 | `artists` | `artist` | +| 灏侀潰鍥?| `pic`/`mPic`/`sPic` | `picUrl` | +| 鏃堕暱 | `interval` (m:ss) | `duration` | +| 鍒楄〃 | `list` | `songs` | + +## 鍥涖€佸悇骞冲彴韪╁潙鐐? +### QQ闊充箰 (tx) +- 鎼滅储绛惧悕锛歚zzcSign` = SHA1 + 绱㈠紩鎻愬彇 + XOR + base64 +- 灏侀潰鍥撅細鏈変笓杈慖D鐢?T002锛屾棤涓撹緫ID鐢?T001 +- getUrl 闊宠川鍙傛暟锛氬甫 `k` (128k/320k/999k) + +### 閰风嫍闊充箰 (kg) +- 鎼滅储杩斿洖鍊煎瓧娈垫槸 `errcode`锛堜笉鏄?`error_code`锛?- 灏侀潰鍥撅細鏇挎崲 `{size}` 涓?`400` + +### 閰锋垜闊充箰 (kw) +- 闊宠川淇℃伅鍦?`N_MINFO` 瀛楁瑙f瀽 +- 灏侀潰鍥撅細`https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` + +### 缃戞槗浜戦煶涔?(wy) +- 鎼滅储鐢?GET锛歚/api/search/get/web`锛屼笉闇€瑕?weapi +- 灏侀潰鍥撅細`picId` 闇€ Base64 缂栫爜 +- getUrl 闊宠川鍙傛暟锛氭暟瀛楁牸寮?`128000`/`320000`/`999000` +- Cookie 鐢ㄤ簬 weapi 鍔犲瘑鎺ュ彛锛堟瘡鏃ユ帹鑽愩€佺浜篎M銆佸枩娆㈡瓕鏇层€佹瓕鍗曘€佷笓杈戙€佹瓕璇嶏級 + +### 鍜挄闊充箰 (mg) +- 鎼滅储闇€ MD5 绛惧悕 +- 灏侀潰鍥撅細鐩稿璺緞闇€鎷兼帴 `https://d.musicapp.migu.cn` + +### GIT闊虫簮 (git) +- 鏃犳悳绱紝绾煶婧?- getUrl 闊宠川鍙傛暟锛歚128k`/`320k`/`999k` + +## 浜斻€乬etUrl 瀹圭伨閫昏緫 + +鎵€鏈夊钩鍙颁娇鐢?*骞跺彂娴嬮€?*妯″紡锛氬悓鏃惰姹傚涓?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.1** +- 鏂囦欢鍚嶆牸寮忥細`Koneko_{骞冲彴鍚峿_v{鐗堟湰鍙穧.js` + +## 鍏€佸父瑙佹姤閿? +| 鎶ラ敊 | 鍘熷洜 | 瑙e喅 | +|------|------|------| +| `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`锛寃y 鐢?`320000` | +| 灏侀潰鍥句笉鏄剧ず | URL 鏍煎紡閿欒鎴栬法鍩?| 妫€鏌ュ悇骞冲彴灏侀潰鍥炬嫾鎺ヨ鍒?| + +## 涔濄€佸悗缁紑鍙戝缓璁? +1. 姣忔淇敼鍏ㄩ儴骞冲彴缁熶竴鍗囩骇鐗堟湰鍙?2. 鍏堝湪娴忚鍣?curl 娴嬭瘯 API 鏄惁鍙敤 +3. 娉ㄦ剰 Javet 鍏煎鎬э紝閬垮厤鐜颁唬 JS 璇硶 +4. 鎼滅储杩斿洖鍔″繀鍖呭惈 `list` 瀛楁 +5. getUrl 娉ㄦ剰鍚勫钩鍙伴煶璐ㄥ弬鏁版牸寮忓樊寮?6. 鎵€鏈夊紓姝ユ搷浣滃姞 `.catch()` 鍏滃簳 +7. 浼樺厛浣跨敤瀹樻柟鎼滅储鎺ュ彛锛岄煶婧愮敤绗笁鏂?API 瀹圭伨 diff --git a/Koneko插件开发避坑指南_v0.0.4.md b/Koneko插件开发避坑指南_v0.0.4.md deleted file mode 100644 index dba8f36..0000000 --- a/Koneko插件开发避坑指南_v0.0.4.md +++ /dev/null @@ -1,146 +0,0 @@ -# Koneko QZ Music v2/v3 插件开发避坑指南 - -> 版本: 0.0.4 | 作者: 云汀(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.4** -- 文件名格式:`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 容灾 diff --git a/QZMusicV2 插件规范│QZ-Music-Plugin-Development-Guide.md b/QZMusicV2 插件规范│QZ-Music-Plugin-Development-Guide.md index 2cd9608..a24306e 100644 --- a/QZMusicV2 插件规范│QZ-Music-Plugin-Development-Guide.md +++ b/QZMusicV2 插件规范│QZ-Music-Plugin-Development-Guide.md @@ -1,119 +1,104 @@ -# QZ Music 插件开发帮助文档 - -## 目录 -1. [概述](#概述) -2. [核心设计原则](#核心设计原则) -3. [从 LX Music 迁移指南](#从-lx-music-迁移指南) -4. [插件基本结构](#插件基本结构) -5. [数据格式规范](#数据格式规范) -6. [示例代码详解](#示例代码详解) -7. [平台配置参考](#平台配置参考) -8. [常见问题](#常见问题) +# QZ Music 鎻掍欢寮€鍙戝府鍔╂枃妗? +## 鐩綍 +1. [姒傝堪](#姒傝堪) +2. [鏍稿績璁捐鍘熷垯](#鏍稿績璁捐鍘熷垯) +3. [浠?LX Music 杩佺Щ鎸囧崡](#浠?lx-music-杩佺Щ鎸囧崡) +4. [鎻掍欢鍩烘湰缁撴瀯](#鎻掍欢鍩烘湰缁撴瀯) +5. [鏁版嵁鏍煎紡瑙勮寖](#鏁版嵁鏍煎紡瑙勮寖) +6. [绀轰緥浠g爜璇﹁В](#绀轰緥浠g爜璇﹁В) +7. [骞冲彴閰嶇疆鍙傝€僝(#骞冲彴閰嶇疆鍙傝€? +8. [甯歌闂](#甯歌闂) --- -## 概述 +## 姒傝堪 -本文档用于指导开发者编写 QZ Music 音源插件。QZ Music 使用 Node.js 运行时环境,插件采用 CommonJS 模块规范,通过 `module.exports` 导出功能接口。 - -### 与 LX Music 的区别 - -| 特性 | QZ Music | LX Music | +鏈枃妗g敤浜庢寚瀵煎紑鍙戣€呯紪鍐?QZ Music 闊虫簮鎻掍欢銆俀Z Music 浣跨敤 Node.js 杩愯鏃剁幆澧冿紝鎻掍欢閲囩敤 CommonJS 妯″潡瑙勮寖锛岄€氳繃 `module.exports` 瀵煎嚭鍔熻兘鎺ュ彛銆? +### 涓?LX Music 鐨勫尯鍒? +| 鐗规€?| QZ Music | LX Music | |------|----------|----------| -| 运行时 | Node.js | JavaScript 运行时 | -| 模块规范 | CommonJS | 全局事件监听 | -| 导出方式 | `module.exports` | `send(EVENT_NAMES.inited)` | -| 通信方式 | 直接函数调用 | 事件驱动 | -| HTTP 请求 | `axios` 或内置 `httpFetch` | `globalThis.lx.request` | +| 杩愯鏃?| Node.js | JavaScript 杩愯鏃?| +| 妯″潡瑙勮寖 | CommonJS | 鍏ㄥ眬浜嬩欢鐩戝惉 | +| 瀵煎嚭鏂瑰紡 | `module.exports` | `send(EVENT_NAMES.inited)` | +| 閫氫俊鏂瑰紡 | 鐩存帴鍑芥暟璋冪敤 | 浜嬩欢椹卞姩 | +| HTTP 璇锋眰 | `axios` 鎴栧唴缃?`httpFetch` | `globalThis.lx.request` | -### 插件类型 - -QZ Music 插件属于**完整音源插件**,核心功能包括: -- 搜索歌曲 -- 获取音频播放 URL -- 获取歌词 -- 获取歌单/专辑信息 -- 获取热搜/排行榜 +### 鎻掍欢绫诲瀷 +QZ Music 鎻掍欢灞炰簬**瀹屾暣闊虫簮鎻掍欢**锛屾牳蹇冨姛鑳藉寘鎷細 +- 鎼滅储姝屾洸 +- 鑾峰彇闊抽鎾斁 URL +- 鑾峰彇姝岃瘝 +- 鑾峰彇姝屽崟/涓撹緫淇℃伅 +- 鑾峰彇鐑悳/鎺掕姒? --- -## 核心设计原则 +## 鏍稿績璁捐鍘熷垯 -### 1. 单平台原则(重要) - -**每个插件只支持一个音乐平台**,例如: -- ✅ 网易云音乐插件(仅支持 wy) -- ✅ QQ音乐插件(仅支持 tx) -- ❌ 聚合插件(同时支持 wy + tx + kw) - -**原因说明**: -``` -用户向插件传配置只能通过环境变量(env) -如果插件支持多平台,切换平台时需要修改环境变量,操作繁琐 -单平台插件更清晰,维护成本低,不易出现"代码屎山" +### 1. 鍗曞钩鍙板師鍒欙紙閲嶈锛? +**姣忎釜鎻掍欢鍙敮鎸佷竴涓煶涔愬钩鍙?*锛屼緥濡傦細 +- 鉁?缃戞槗浜戦煶涔愭彃浠讹紙浠呮敮鎸?wy锛?- 鉁?QQ闊充箰鎻掍欢锛堜粎鏀寔 tx锛?- 鉂?鑱氬悎鎻掍欢锛堝悓鏃舵敮鎸?wy + tx + kw锛? +**鍘熷洜璇存槑**锛?``` +鐢ㄦ埛鍚戞彃浠朵紶閰嶇疆鍙兘閫氳繃鐜鍙橀噺锛坋nv锛?濡傛灉鎻掍欢鏀寔澶氬钩鍙帮紝鍒囨崲骞冲彴鏃堕渶瑕佷慨鏀圭幆澧冨彉閲忥紝鎿嶄綔绻佺悙 +鍗曞钩鍙版彃浠舵洿娓呮櫚锛岀淮鎶ゆ垚鏈綆锛屼笉鏄撳嚭鐜?浠g爜灞庡北" ``` -**如需多平台支持**:建议自建后端服务,统一处理搜索和 URL 获取,前端插件只作为代理。 - -### 2. 配置方式 - -通过 `process.env` 或插件内置的 `env` 配置读取环境变量: +**濡傞渶澶氬钩鍙版敮鎸?*锛氬缓璁嚜寤哄悗绔湇鍔★紝缁熶竴澶勭悊鎼滅储鍜?URL 鑾峰彇锛屽墠绔彃浠跺彧浣滀负浠g悊銆? +### 2. 閰嶇疆鏂瑰紡 +閫氳繃 `process.env` 鎴栨彃浠跺唴缃殑 `env` 閰嶇疆璇诲彇鐜鍙橀噺锛? ```javascript -// 读取用户配置的 API 密钥 +// 璇诲彇鐢ㄦ埛閰嶇疆鐨?API 瀵嗛挜 const API_KEY = process.env.API_KEY || '' -// 读取自定义服务端地址 -const CUSTOM_SERVER = process.env.SERVER_URL || '默认地址' +// 璇诲彇鑷畾涔夋湇鍔$鍦板潃 +const CUSTOM_SERVER = process.env.SERVER_URL || '榛樿鍦板潃' ``` -### 3. 支持的音质标识 - -| 标识 | 说明 | +### 3. 鏀寔鐨勯煶璐ㄦ爣璇? +| 鏍囪瘑 | 璇存槑 | |------|------| -| `128k` | 标准音质,MP3 格式 | -| `320k` | 高品音质,MP3 格式 | -| `flac` | 无损音质,16bit FLAC | -| `flac24bit` | 无损音质,24bit FLAC | -| `hires` | 高解析度无损 | +| `128k` | 鏍囧噯闊宠川锛孧P3 鏍煎紡 | +| `320k` | 楂樺搧闊宠川锛孧P3 鏍煎紡 | +| `flac` | 鏃犳崯闊宠川锛?6bit FLAC | +| `flac24bit` | 鏃犳崯闊宠川锛?4bit FLAC | +| `hires` | 楂樿В鏋愬害鏃犳崯 | --- -## 从 LX Music 迁移指南 +## 浠?LX Music 杩佺Щ鎸囧崡 -### 迁移对照表 - -| LX Music | QZ Music | 说明 | +### 杩佺Щ瀵圭収琛? +| LX Music | QZ Music | 璇存槑 | |----------|----------|------| -| `globalThis.lx` | `require` 模块 | 不再需要全局对象 | -| `globalThis.lx.request` | `axios` 或 `httpFetch` | 使用标准 HTTP 库 | -| `globalThis.lx.env` | `process.env` | 环境变量读取方式 | -| `on(EVENT_NAMES.request, ...)` | 直接导出函数 | 改为函数导出 | -| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 改为模块导出 | -| `musicInfo.songmid` | `musicInfo.id` | 字段名可能不同 | -| `info.type` | `quality` 参数 | 音质参数位置 | +| `globalThis.lx` | `require` 妯″潡 | 涓嶅啀闇€瑕佸叏灞€瀵硅薄 | +| `globalThis.lx.request` | `axios` 鎴?`httpFetch` | 浣跨敤鏍囧噯 HTTP 搴?| +| `globalThis.lx.env` | `process.env` | 鐜鍙橀噺璇诲彇鏂瑰紡 | +| `on(EVENT_NAMES.request, ...)` | 鐩存帴瀵煎嚭鍑芥暟 | 鏀逛负鍑芥暟瀵煎嚭 | +| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 鏀逛负妯″潡瀵煎嚭 | +| `musicInfo.songmid` | `musicInfo.id` | 瀛楁鍚嶅彲鑳戒笉鍚?| +| `info.type` | `quality` 鍙傛暟 | 闊宠川鍙傛暟浣嶇疆 | -### 迁移步骤 +### 杩佺Щ姝ラ -#### 步骤 1:修改模块导入 - -**LX Music 原代码:** +#### 姝ラ 1锛氫慨鏀规ā鍧楀鍏? +**LX Music 鍘熶唬鐮侊細** ```javascript const { EVENT_NAMES, request, on, send, env, version } = globalThis.lx ``` -**QZ Music 新代码:** +**QZ Music 鏂颁唬鐮侊細** ```javascript const axios = require('axios') const crypto = require('crypto') -// 环境变量 +// 鐜鍙橀噺 const API_KEY = process.env.API_KEY || '' ``` -#### 步骤 2:修改 HTTP 请求 +#### 姝ラ 2锛氫慨鏀?HTTP 璇锋眰 -**LX Music 原代码:** +**LX Music 鍘熶唬鐮侊細** ```javascript function httpRequest(url) { return new Promise((resolve, reject) => { @@ -125,7 +110,7 @@ function httpRequest(url) { } ``` -**QZ Music 新代码:** +**QZ Music 鏂颁唬鐮侊細** ```javascript async function httpRequest(url, options = {}) { const response = await axios({ @@ -138,11 +123,10 @@ async function httpRequest(url, options = {}) { } ``` -#### 步骤 3:修改函数导出 - -**LX Music 原代码:** +#### 姝ラ 3锛氫慨鏀瑰嚱鏁板鍑? +**LX Music 鍘熶唬鐮侊細** ```javascript -// 事件监听方式 +// 浜嬩欢鐩戝惉鏂瑰紡 on(EVENT_NAMES.request, ({ action, source, info }) => { switch (action) { case 'musicUrl': @@ -150,72 +134,68 @@ on(EVENT_NAMES.request, ({ action, source, info }) => { } }) -// 初始化事件 -send(EVENT_NAMES.inited, { +// 鍒濆鍖栦簨浠?send(EVENT_NAMES.inited, { status: true, sources: musicSource }) ``` -**QZ Music 新代码:** +**QZ Music 鏂颁唬鐮侊細** ```javascript -// 直接导出函数 +// 鐩存帴瀵煎嚭鍑芥暟 module.exports = { - // 搜索功能 + // 鎼滅储鍔熻兘 musicSearch, - // 获取音频 URL + // 鑾峰彇闊抽 URL getUrl, - // 获取歌词 + // 鑾峰彇姝岃瘝 getLyric, - // 获取歌单 + // 鑾峰彇姝屽崟 songList, - // 获取专辑 + // 鑾峰彇涓撹緫 album, - // 获取热搜 + // 鑾峰彇鐑悳 hotSearch, - // 插件信息 + // 鎻掍欢淇℃伅 pluginInfo: { - info: { id: 'wy', name: '网易云', version: '3' }, + info: { id: 'wy', name: '缃戞槗浜?, version: '3' }, quality: [...], supportFunc: [...] } } ``` -#### 步骤 4:修改返回数据格式 - -**LX Music 原代码:** +#### 姝ラ 4锛氫慨鏀硅繑鍥炴暟鎹牸寮? +**LX Music 鍘熶唬鐮侊細** ```javascript -// 直接返回 URL 字符串 -return 'https://example.com/music.mp3' +// 鐩存帴杩斿洖 URL 瀛楃涓?return 'https://example.com/music.mp3' -// 或返回歌词对象 -return { - lyric: '[00:00.000]歌词内容', - tlyric: '[00:00.000]翻译歌词' +// 鎴栬繑鍥炴瓕璇嶅璞?return { + lyric: '[00:00.000]姝岃瘝鍐呭', + tlyric: '[00:00.000]缈昏瘧姝岃瘝' } ``` -**QZ Music 新代码:** +**QZ Music 鏂颁唬鐮侊細** ```javascript -// 搜索返回统一格式 +// 鎼滅储杩斿洖缁熶竴鏍煎紡 return { list: [{ id: '123456', - name: '歌曲名', - artists: '歌手名', + name: '姝屾洸鍚?, + artists: '姝屾墜鍚?, source: 'wy', - pic: '封面URL', - mPic: '中封面URL', - sPic: '小封面URL', - albumName: '专辑名', - albumId: '专辑ID', + pic: '灏侀潰URL', + mPic: '涓皝闈RL', + sPic: '灏忓皝闈RL', + albumName: '涓撹緫鍚?, + albumId: '涓撹緫ID', interval: '03:45', qualities: { '128k': '4.2M', 'flac': '35M' } }], @@ -226,99 +206,89 @@ return { source: 'wy' } -// 歌词返回格式 +// 姝岃瘝杩斿洖鏍煎紡 return { - lyric: '歌词内容', - tlyric: '翻译歌词' + lyric: '姝岃瘝鍐呭', + tlyric: '缈昏瘧姝岃瘝' } ``` --- -## 插件基本结构 +## 鎻掍欢鍩烘湰缁撴瀯 -### 文件头部注释 +### 鏂囦欢澶撮儴娉ㄩ噴 ```javascript /** - * @name 网易云音乐源 - * @description QZ Music 音源插件 + * @name 缃戞槗浜戦煶涔愭簮 + * @description QZ Music 闊虫簮鎻掍欢 * @version 3.0.0 - * @author 开发者 - * @homepage https://github.com/your-repo + * @author 寮€鍙戣€? * @homepage https://github.com/your-repo * @license MIT * - * 支持平台: 网易云音乐 (wy) - * 支持音质: 128k, 320k, flac, flac24bit, hires + * 鏀寔骞冲彴: 缃戞槗浜戦煶涔?(wy) + * 鏀寔闊宠川: 128k, 320k, flac, flac24bit, hires */ ``` -### 核心导入 +### 鏍稿績瀵煎叆 ```javascript 'use strict' -// 标准 Node.js 模块 +// 鏍囧噯 Node.js 妯″潡 const axios = require('axios') const crypto = require('crypto') ``` -### 配置区域 +### 閰嶇疆鍖哄煙 ```javascript -// ========== 用户可配置区域 ========== +// ========== 鐢ㄦ埛鍙厤缃尯鍩?========== -// 服务端地址(用户可通过环境变量覆盖) -const API_BASE = process.env.SERVER_URL || 'https://your-server.com' +// 鏈嶅姟绔湴鍧€锛堢敤鎴峰彲閫氳繃鐜鍙橀噺瑕嗙洊锛?const API_BASE = process.env.SERVER_URL || 'https://your-server.com' -// API 密钥(用户通过环境变量设置) -const API_KEY = process.env.API_KEY || '' +// API 瀵嗛挜锛堢敤鎴烽€氳繃鐜鍙橀噺璁剧疆锛?const API_KEY = process.env.API_KEY || '' -// 当前平台标识(单平台插件固定值) -const PLATFORM = 'wy' // wy: 网易云, tx: QQ音乐, kw: 酷我, kg: 酷狗, mg: 咪咕 +// 褰撳墠骞冲彴鏍囪瘑锛堝崟骞冲彴鎻掍欢鍥哄畾鍊硷級 +const PLATFORM = 'wy' // wy: 缃戞槗浜? tx: QQ闊充箰, kw: 閰锋垜, kg: 閰风嫍, mg: 鍜挄 -// 支持的音质列表 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac', 'flac24bit', 'hires'] +// 鏀寔鐨勯煶璐ㄥ垪琛?const SUPPORT_QUALITIES = ['128k', '320k', 'flac', 'flac24bit', 'hires'] ``` -### 导出结构 +### 瀵煎嚭缁撴瀯 ```javascript -// ========== 插件导出 ========== +// ========== 鎻掍欢瀵煎嚭 ========== module.exports = { - // 核心功能(必须实现) - musicSearch, // 歌曲搜索 - getUrl, // 获取音频 URL + // 鏍稿績鍔熻兘锛堝繀椤诲疄鐜帮級 + musicSearch, // 姝屾洸鎼滅储 + getUrl, // 鑾峰彇闊抽 URL - // 可选功能 - getLyric, // 获取歌词 - songList, // 歌单详情 - album, // 专辑详情 - hotSearch, // 热搜词 - tipSearch, // 搜索提示 - leaderboard, // 排行榜 - - // 插件信息(必须) + // 鍙€夊姛鑳? getLyric, // 鑾峰彇姝岃瘝 + songList, // 姝屽崟璇︽儏 + album, // 涓撹緫璇︽儏 + hotSearch, // 鐑悳璇? tipSearch, // 鎼滅储鎻愮ず + leaderboard, // 鎺掕姒? + // 鎻掍欢淇℃伅锛堝繀椤伙級 pluginInfo: { info: { - id: 'wy', // 平台标识 - name: '网易云', // 显示名称 - description: '网易云音乐插件', // 描述 - version: '3' // 版本号 - }, - env: [ // 环境变量配置 - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + id: 'wy', // 骞冲彴鏍囪瘑 + name: '缃戞槗浜?, // 鏄剧ず鍚嶇О + description: '缃戞槗浜戦煶涔愭彃浠?, // 鎻忚堪 + version: '3' // 鐗堟湰鍙? }, + env: [ // 鐜鍙橀噺閰嶇疆 + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], - ext: [], // 扩展功能 - quality: [ // 支持的音质 - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' }, + ext: [], // 鎵╁睍鍔熻兘 + quality: [ // 鏀寔鐨勯煶璐? { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' }, { name: 'Hi-Res', ui: 'HR', id: 'hires' } ], - supportFunc: [ // 支持的功能 - 'search_song', + supportFunc: [ // 鏀寔鐨勫姛鑳? 'search_song', 'search_playlist', 'playlist', 'album', @@ -330,25 +300,19 @@ module.exports = { --- -## 数据格式规范 +## 鏁版嵁鏍煎紡瑙勮寖 -### 搜索结果统一格式 +### 鎼滅储缁撴灉缁熶竴鏍煎紡 ```javascript { list: [ { - id: String, // 歌曲唯一标识 - name: String, // 歌曲名(已解码 HTML 实体) - artists: String, // 歌手名(用 "、" 分隔) - source: String, // 平台标识: 'wy'/'tx'/'kw'/'kg'/'mg' - pic: String, // 大封面图 URL(500x500) - mPic: String, // 中封面图 URL(300x300) - sPic: String, // 小封面图 URL(150x150) - albumName: String, // 专辑名 - albumId: String, // 专辑 ID - interval: String, // 时长 "mm:ss" - qualities: { // 音质 -> 文件大小 + id: String, // 姝屾洸鍞竴鏍囪瘑 + name: String, // 姝屾洸鍚嶏紙宸茶В鐮?HTML 瀹炰綋锛? artists: String, // 姝屾墜鍚嶏紙鐢?"銆? 鍒嗛殧锛? source: String, // 骞冲彴鏍囪瘑: 'wy'/'tx'/'kw'/'kg'/'mg' + pic: String, // 澶у皝闈㈠浘 URL锛?00x500锛? mPic: String, // 涓皝闈㈠浘 URL锛?00x300锛? sPic: String, // 灏忓皝闈㈠浘 URL锛?50x150锛? albumName: String, // 涓撹緫鍚? albumId: String, // 涓撹緫 ID + interval: String, // 鏃堕暱 "mm:ss" + qualities: { // 闊宠川 -> 鏂囦欢澶у皬 '128k': '4.2M', '320k': '8.5M', 'flac': '35M', @@ -356,108 +320,95 @@ module.exports = { } } ], - total: Number, // 总数量 - page: Number, // 当前页码 - limit: Number, // 每页数量 - allPage: Number, // 总页数 - source: String // 平台标识 + total: Number, // 鎬绘暟閲? page: Number, // 褰撳墠椤电爜 + limit: Number, // 姣忛〉鏁伴噺 + allPage: Number, // 鎬婚〉鏁? source: String // 骞冲彴鏍囪瘑 } ``` -### 歌词统一格式 +### 姝岃瘝缁熶竴鏍煎紡 ```javascript { - lyric: String, // 普通歌词 (LRC 格式) - tlyric: String, // 翻译歌词 - qrc: String, // 逐字歌词 (QRC 格式) - roma: String // 音译歌词 + lyric: String, // 鏅€氭瓕璇?(LRC 鏍煎紡) + tlyric: String, // 缈昏瘧姝岃瘝 + qrc: String, // 閫愬瓧姝岃瘝 (QRC 鏍煎紡) + roma: String // 闊宠瘧姝岃瘝 } -// 注意:至少返回 lyric 或 qrc 之一 +// 娉ㄦ剰锛氳嚦灏戣繑鍥?lyric 鎴?qrc 涔嬩竴 ``` -### 歌单详情统一格式 +### 姝屽崟璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 歌曲列表,格式同搜索结果 */], + list: [/* 姝屾洸鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 歌单名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 作者 - } + name: String, // 姝屽崟鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 浣滆€? } } ``` -### 专辑详情统一格式 +### 涓撹緫璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 歌曲列表,格式同搜索结果 */], + list: [/* 姝屾洸鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 专辑名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 艺术家 - } + name: String, // 涓撹緫鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 鑹烘湳瀹? } } ``` --- -## 示例代码详解 +## 绀轰緥浠g爜璇﹁В -### 完整插件模板 +### 瀹屾暣鎻掍欢妯℃澘 ```javascript /** - * @name 网易云音乐源 - * @description QZ Music 音源插件示例 + * @name 缃戞槗浜戦煶涔愭簮 + * @description QZ Music 闊虫簮鎻掍欢绀轰緥 * @version 3.0.0 - * @author 开发者 - * - * 支持平台: 网易云音乐 (wy) - * 支持音质: 128k, 320k, flac + * @author 寮€鍙戣€? * + * 鏀寔骞冲彴: 缃戞槗浜戦煶涔?(wy) + * 鏀寔闊宠川: 128k, 320k, flac */ 'use strict' -// ==================== 核心导入 ==================== +// ==================== 鏍稿績瀵煎叆 ==================== const axios = require('axios') const crypto = require('crypto') -// ==================== 配置区域 ==================== +// ==================== 閰嶇疆鍖哄煙 ==================== -// 平台标识(固定值,单平台插件) +// 骞冲彴鏍囪瘑锛堝浐瀹氬€硷紝鍗曞钩鍙版彃浠讹級 const PLATFORM = 'wy' -// 服务端配置 -const CONFIG = { +// 鏈嶅姟绔厤缃?const CONFIG = { serverUrl: process.env.SERVER_URL || 'https://api.example.com', apiKey: process.env.API_KEY || '', timeout: 10000 } -// 支持的音质 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] +// 鏀寔鐨勯煶璐?const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] -// ==================== 工具函数 ==================== +// ==================== 宸ュ叿鍑芥暟 ==================== /** - * 文件大小格式化 - * @param {number} size - 字节数 - * @returns {string} 格式化后的字符串 + * 鏂囦欢澶у皬鏍煎紡鍖? * @param {number} size - 瀛楄妭鏁? * @returns {string} 鏍煎紡鍖栧悗鐨勫瓧绗︿覆 */ function sizeFormate(size) { if (!size || isNaN(size)) return '' @@ -468,9 +419,8 @@ function sizeFormate(size) { } /** - * 播放时间格式化 - * @param {number} time - 秒数 - * @returns {string} 格式化后的字符串 + * 鎾斁鏃堕棿鏍煎紡鍖? * @param {number} time - 绉掓暟 + * @returns {string} 鏍煎紡鍖栧悗鐨勫瓧绗︿覆 */ function formatPlayTime(time) { if (!time || isNaN(time)) return '--/--' @@ -480,20 +430,18 @@ function formatPlayTime(time) { } /** - * 歌手名称格式化 - * @param {Array} singerList - 歌手列表 - * @returns {string} 用 "、" 连接的歌手名 + * 姝屾墜鍚嶇О鏍煎紡鍖? * @param {Array} singerList - 姝屾墜鍒楄〃 + * @returns {string} 鐢?"銆? 杩炴帴鐨勬瓕鎵嬪悕 */ function formatSingerName(singerList) { if (!singerList || !Array.isArray(singerList)) return '' - return singerList.map(s => s.name || s).join('、') + return singerList.map(s => s.name || s).join('銆?) } /** - * HTML 实体解码 - * @param {string} str - 含 HTML 实体的字符串 - * @returns {string} 解码后的字符串 - */ + * HTML 瀹炰綋瑙g爜 + * @param {string} str - 鍚?HTML 瀹炰綋鐨勫瓧绗︿覆 + * @returns {string} 瑙g爜鍚庣殑瀛楃涓? */ function decodeName(str) { if (!str) return '' return str @@ -505,35 +453,31 @@ function decodeName(str) { .replace(/ /g, ' ') } -// ==================== 核心功能 ==================== +// ==================== 鏍稿績鍔熻兘 ==================== /** - * 搜索歌曲 - * @param {string} str - 搜索关键词 - * @param {number} page - 页码,从 1 开始 - * @param {number} limit - 每页数量 - * @returns {Promise} 搜索结果 + * 鎼滅储姝屾洸 + * @param {string} str - 鎼滅储鍏抽敭璇? * @param {number} page - 椤电爜锛屼粠 1 寮€濮? * @param {number} limit - 姣忛〉鏁伴噺 + * @returns {Promise} 鎼滅储缁撴灉 */ async function musicSearch(str, page = 1, limit = 20) { - // 构造请求参数 - const params = { + // 鏋勯€犺姹傚弬鏁? const params = { keyword: str, page: page, limit: limit } - // 发送请求 - const url = `${CONFIG.serverUrl}/search` + // 鍙戦€佽姹? const url = `${CONFIG.serverUrl}/search` const response = await axios.get(url, { params, headers: { 'X-API-Key': CONFIG.apiKey }, timeout: CONFIG.timeout }) - // 解析数据 + // 瑙f瀽鏁版嵁 const data = response.data - // 转换为统一格式 + // 杞崲涓虹粺涓€鏍煎紡 const list = data.songs.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -563,19 +507,17 @@ async function musicSearch(str, page = 1, limit = 20) { } /** - * 获取音乐播放 URL - * @param {string} songId - 歌曲 ID - * @param {string} quality - 音质标识 - * @returns {Promise} 播放 URL + * 鑾峰彇闊充箰鎾斁 URL + * @param {string} songId - 姝屾洸 ID + * @param {string} quality - 闊宠川鏍囪瘑 + * @returns {Promise} 鎾斁 URL */ async function getUrl(songId, quality) { - // 检查音质 - if (!SUPPORT_QUALITIES.includes(quality)) { + // 妫€鏌ラ煶璐? if (!SUPPORT_QUALITIES.includes(quality)) { quality = SUPPORT_QUALITIES[0] } - // 发送请求 - const url = `${CONFIG.serverUrl}/music/url` + // 鍙戦€佽姹? const url = `${CONFIG.serverUrl}/music/url` const response = await axios.get(url, { params: { id: songId, quality: quality }, headers: { 'X-API-Key': CONFIG.apiKey }, @@ -585,16 +527,16 @@ async function getUrl(songId, quality) { const data = response.data if (!data.url || !data.url.startsWith('http')) { - throw new Error('获取链接失败') + throw new Error('鑾峰彇閾炬帴澶辫触') } return data.url } /** - * 获取歌词 - * @param {string} songId - 歌曲 ID - * @returns {Promise} 歌词对象 + * 鑾峰彇姝岃瘝 + * @param {string} songId - 姝屾洸 ID + * @returns {Promise} 姝岃瘝瀵硅薄 */ async function getLyric(songId) { const url = `${CONFIG.serverUrl}/music/lyric` @@ -613,11 +555,11 @@ async function getLyric(songId) { } /** - * 获取歌单详情 - * @param {string} id - 歌单 ID - * @param {number} page - 页码 - * @param {number} limit - 每页数量 - * @returns {Promise} 歌单详情 + * 鑾峰彇姝屽崟璇︽儏 + * @param {string} id - 姝屽崟 ID + * @param {number} page - 椤电爜 + * @param {number} limit - 姣忛〉鏁伴噺 + * @returns {Promise} 姝屽崟璇︽儏 */ async function songList(id, page = 1, limit = 20) { const url = `${CONFIG.serverUrl}/playlist` @@ -629,7 +571,7 @@ async function songList(id, page = 1, limit = 20) { const data = response.data - // 转换歌曲列表 + // 杞崲姝屾洸鍒楄〃 const list = data.songs.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -660,10 +602,10 @@ async function songList(id, page = 1, limit = 20) { } /** - * 获取专辑详情 - * @param {string} id - 专辑 ID - * @param {number} page - 页码 - * @returns {Promise} 专辑详情 + * 鑾峰彇涓撹緫璇︽儏 + * @param {string} id - 涓撹緫 ID + * @param {number} page - 椤电爜 + * @returns {Promise} 涓撹緫璇︽儏 */ async function album(id, page = 1) { const url = `${CONFIG.serverUrl}/album` @@ -675,7 +617,7 @@ async function album(id, page = 1) { const data = response.data - // 转换歌曲列表 + // 杞崲姝屾洸鍒楄〃 const list = data.songs.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -706,8 +648,7 @@ async function album(id, page = 1) { } /** - * 获取热搜词 - * @returns {Promise} 热搜列表 + * 鑾峰彇鐑悳璇? * @returns {Promise} 鐑悳鍒楄〃 */ async function hotSearch() { const url = `${CONFIG.serverUrl}/hotsearch` @@ -722,10 +663,10 @@ async function hotSearch() { } } -// ==================== 插件导出 ==================== +// ==================== 鎻掍欢瀵煎嚭 ==================== module.exports = { - // 核心功能 + // 鏍稿績鍔熻兘 musicSearch, getUrl, getLyric, @@ -733,23 +674,23 @@ module.exports = { album, hotSearch, - // 插件信息 + // 鎻掍欢淇℃伅 pluginInfo: { info: { id: PLATFORM, - name: '网易云', - description: '网易云音乐插件', + name: '缃戞槗浜?, + description: '缃戞槗浜戦煶涔愭彃浠?, version: '3' }, env: [ - { key: 'SERVER_URL', name: '服务端地址', description: '自定义服务端地址' }, - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + { key: 'SERVER_URL', name: '鏈嶅姟绔湴鍧€', description: '鑷畾涔夋湇鍔$鍦板潃' }, + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], ext: [], quality: [ - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' } + { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' } ], supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] } @@ -758,24 +699,21 @@ module.exports = { --- -## 平台配置参考 +## 骞冲彴閰嶇疆鍙傝€? +### 鍚勫钩鍙版爣璇嗗鐓ц〃 -### 各平台标识对照表 - -| 平台 | 标识 | 常见歌曲 ID 字段 | +| 骞冲彴 | 鏍囪瘑 | 甯歌姝屾洸 ID 瀛楁 | |------|------|-----------------| -| 网易云音乐 | `wy` | `id`, `songmid` | -| QQ音乐 | `tx` | `songmid`, `id` | -| 酷我音乐 | `kw` | `rid`, `id`, `hash` | -| 酷狗音乐 | `kg` | `hash`, `id` | -| 咪咕音乐 | `mg` | `copyrightId`, `id` | -| 汽水音乐 | `qx` | `id` | - -### 音质支持参考 +| 缃戞槗浜戦煶涔?| `wy` | `id`, `songmid` | +| QQ闊充箰 | `tx` | `songmid`, `id` | +| 閰锋垜闊充箰 | `kw` | `rid`, `id`, `hash` | +| 閰风嫍闊充箰 | `kg` | `hash`, `id` | +| 鍜挄闊充箰 | `mg` | `copyrightId`, `id` | +| 姹芥按闊充箰 | `qx` | `id` | +### 闊宠川鏀寔鍙傝€? ```javascript -// 各平台常见音质配置 -const PLATFORM_QUALITIES = { +// 鍚勫钩鍙板父瑙侀煶璐ㄩ厤缃?const PLATFORM_QUALITIES = { wy: ['128k', '320k', 'flac', 'flac24bit', 'hires'], tx: ['128k', '320k', 'flac', 'flac24bit', 'hires'], kw: ['128k', '320k', 'flac', 'flac24bit'], @@ -787,70 +725,59 @@ const PLATFORM_QUALITIES = { --- -## 常见问题 +## 甯歌闂 -### Q1: 如何从 LX Music 迁移到 QZ Music? +### Q1: 濡備綍浠?LX Music 杩佺Щ鍒?QZ Music锛? +**A**: 鍙傝€冩湰鏂囨。鐨?浠?LX Music 杩佺Щ鎸囧崡"绔犺妭锛屼富瑕佷慨鏀圭偣锛?1. 灏?`globalThis.lx.request` 鏀逛负 `axios` +2. 灏嗕簨浠剁洃鍚敼涓哄嚱鏁板鍑?3. 淇敼鏁版嵁杩斿洖鏍煎紡 +4. 娣诲姞 `module.exports` 瀵煎嚭 -**A**: 参考本文档的"从 LX Music 迁移指南"章节,主要修改点: -1. 将 `globalThis.lx.request` 改为 `axios` -2. 将事件监听改为函数导出 -3. 修改数据返回格式 -4. 添加 `module.exports` 导出 +### Q2: 鎻掍欢鍔犺浇澶辫触鎬庝箞鍔烇紵 -### Q2: 插件加载失败怎么办? +**A**: 妫€鏌ヤ互涓嬪嚑鐐癸細 +1. 鏂囦欢璇硶鏄惁姝g‘锛歚node -c your-plugin.js` +2. `pluginInfo` 鏄惁瀹屾暣 +3. 瀵煎嚭鐨勫嚱鏁板悕鏄惁姝g‘ +4. 渚濊禆妯″潡鏄惁宸插畨瑁咃紙濡?`axios`锛? +### Q3: 鎼滅储杩斿洖绌虹粨鏋滐紵 -**A**: 检查以下几点: -1. 文件语法是否正确:`node -c your-plugin.js` -2. `pluginInfo` 是否完整 -3. 导出的函数名是否正确 -4. 依赖模块是否已安装(如 `axios`) +**A**: 妫€鏌ワ細 +1. API 璇锋眰鏄惁鎴愬姛锛堟煡鐪嬫棩蹇楋級 +2. 鍝嶅簲鏁版嵁瑙f瀽鏄惁姝g‘ +3. 鏁版嵁鏍煎紡鏄惁绗﹀悎瑙勮寖 +4. 杩斿洖鐨?`source` 鏄惁涓庡钩鍙版爣璇嗕竴鑷? +### Q4: 闊抽鏃犳硶鎾斁锛? +**A**: 妫€鏌ワ細 +1. `getUrl` 杩斿洖鐨?URL 鏄惁鏈夋晥 +2. URL 鏄惁浠?`http` 寮€澶?3. 闊宠川鏍囪瘑鏄惁姝g‘ +4. 鏄惁鏈夎法鍩熸垨鏉冮檺闂 -### Q3: 搜索返回空结果? - -**A**: 检查: -1. API 请求是否成功(查看日志) -2. 响应数据解析是否正确 -3. 数据格式是否符合规范 -4. 返回的 `source` 是否与平台标识一致 - -### Q4: 音频无法播放? - -**A**: 检查: -1. `getUrl` 返回的 URL 是否有效 -2. URL 是否以 `http` 开头 -3. 音质标识是否正确 -4. 是否有跨域或权限问题 - -### Q5: 用户如何配置插件? - -**A**: 用户通过 QZ Music 的设置界面配置环境变量: +### Q5: 鐢ㄦ埛濡備綍閰嶇疆鎻掍欢锛? +**A**: 鐢ㄦ埛閫氳繃 QZ Music 鐨勮缃晫闈㈤厤缃幆澧冨彉閲忥細 ``` -SERVER_URL = 自定义服务端地址 -API_KEY = 用户的API密钥 +SERVER_URL = 鑷畾涔夋湇鍔$鍦板潃 +API_KEY = 鐢ㄦ埛鐨凙PI瀵嗛挜 ``` -插件通过 `process.env` 读取: - +鎻掍欢閫氳繃 `process.env` 璇诲彇锛? ```javascript -const SERVER_URL = process.env.SERVER_URL || '默认地址' +const SERVER_URL = process.env.SERVER_URL || '榛樿鍦板潃' ``` --- -## 附录 +## 闄勫綍 -### 参考资源 +### 鍙傝€冭祫婧? +- QZ Music 瀹樻柟鏂囨。 +- 鎻愪緵鐨勯煶婧愭枃浠讹紙lx&qz瀹樻柟閮ㄥ垎闊虫簮锛?- Node.js 瀹樻柟鏂囨。 +- axios 鏂囨。 -- QZ Music 官方文档 -- 提供的音源文件(lx&qz官方部分音源) -- Node.js 官方文档 -- axios 文档 +### 鐗堟湰鍘嗗彶 -### 版本历史 - -- v1.0.0 (2024-05-28): 初始版本 +- v1.0.0 (2024-05-28): 鍒濆鐗堟湰 --- -**文档结束** +**鏂囨。缁撴潫** diff --git a/QZ_Music-V2 插件规范(v1.0.3).md b/QZ_Music-V2 插件规范(v1.0.3).md index 056bd9f..ba1b7bb 100644 --- a/QZ_Music-V2 插件规范(v1.0.3).md +++ b/QZ_Music-V2 插件规范(v1.0.3).md @@ -1,137 +1,122 @@ -# 插件开发帮助文档 - -> **版本**: v1.0.3 +# 鎻掍欢寮€鍙戝府鍔╂枃妗? +> **鐗堟湰**: v1.0.3 > -> 撰写人: 蜻蜓的好朋友 : Miao-moe +> 鎾板啓浜? 铚昏湏鐨勫ソ鏈嬪弸 : Miao-moe > GitHub: [Miao-moe](https://github.com/Miao-moe) -## 目录 -1. [概述](#概述) -2. [核心设计原则](#核心设计原则) -3. [从其他平台迁移指南](#从其他平台迁移指南) -4. [插件基本结构](#插件基本结构) -5. [数据格式规范](#数据格式规范) -6. [示例代码详解](#示例代码详解) -7. [平台配置参考](#平台配置参考) -8. [开发建议](#开发建议) -9. [常见问题](#常见问题) +## 鐩綍 +1. [姒傝堪](#姒傝堪) +2. [鏍稿績璁捐鍘熷垯](#鏍稿績璁捐鍘熷垯) +3. [浠庡叾浠栧钩鍙拌縼绉绘寚鍗梋(#浠庡叾浠栧钩鍙拌縼绉绘寚鍗? +4. [鎻掍欢鍩烘湰缁撴瀯](#鎻掍欢鍩烘湰缁撴瀯) +5. [鏁版嵁鏍煎紡瑙勮寖](#鏁版嵁鏍煎紡瑙勮寖) +6. [绀轰緥浠g爜璇﹁В](#绀轰緥浠g爜璇﹁В) +7. [骞冲彴閰嶇疆鍙傝€僝(#骞冲彴閰嶇疆鍙傝€? +8. [寮€鍙戝缓璁甝(#寮€鍙戝缓璁? +9. [甯歌闂](#甯歌闂) --- -## 概述 +## 姒傝堪 -本文档用于指导开发者编写音源插件。本系统使用 Node.js 运行时环境,插件采用 CommonJS 模块规范,通过 `module.exports` 导出功能接口。 +鏈枃妗g敤浜庢寚瀵煎紑鍙戣€呯紪鍐欓煶婧愭彃浠躲€傛湰绯荤粺浣跨敤 Node.js 杩愯鏃剁幆澧冿紝鎻掍欢閲囩敤 CommonJS 妯″潡瑙勮寖锛岄€氳繃 `module.exports` 瀵煎嚭鍔熻兘鎺ュ彛銆? +### 涓庡叾浠栫郴缁熺殑鍖哄埆 -### 与其他系统的区别 - -| 特性 | 本系统 | 其他系统 | +| 鐗规€?| 鏈郴缁?| 鍏朵粬绯荤粺 | |------|--------|----------| -| 运行时 | Node.js | JavaScript 运行时 | -| 模块规范 | CommonJS | 全局事件监听 | -| 导出方式 | `module.exports` | 事件发送 | -| 通信方式 | 直接函数调用 | 事件驱动 | -| HTTP 请求 | `axios` 或内置 `httpFetch` | 全局请求对象 | +| 杩愯鏃?| Node.js | JavaScript 杩愯鏃?| +| 妯″潡瑙勮寖 | CommonJS | 鍏ㄥ眬浜嬩欢鐩戝惉 | +| 瀵煎嚭鏂瑰紡 | `module.exports` | 浜嬩欢鍙戦€?| +| 閫氫俊鏂瑰紡 | 鐩存帴鍑芥暟璋冪敤 | 浜嬩欢椹卞姩 | +| HTTP 璇锋眰 | `axios` 鎴栧唴缃?`httpFetch` | 鍏ㄥ眬璇锋眰瀵硅薄 | -### 插件类型 - -本系统插件属于**完整音源插件**,核心功能包括: -- 搜索内容 -- 获取播放 URL -- 获取歌词 -- 获取列表/合集信息 -- 获取热搜/排行榜 +### 鎻掍欢绫诲瀷 +鏈郴缁熸彃浠跺睘浜?*瀹屾暣闊虫簮鎻掍欢**锛屾牳蹇冨姛鑳藉寘鎷細 +- 鎼滅储鍐呭 +- 鑾峰彇鎾斁 URL +- 鑾峰彇姝岃瘝 +- 鑾峰彇鍒楄〃/鍚堥泦淇℃伅 +- 鑾峰彇鐑悳/鎺掕姒? --- -## 核心设计原则 +## 鏍稿績璁捐鍘熷垯 -### 1. 单平台原则(重要) - -**每个插件只支持一个平台**,例如: -- ✅ 平台A插件(仅支持 A) -- ✅ 平台B插件(仅支持 B) -- ❌ 聚合插件(同时支持 A + B + C) - -**原因说明**: -``` -用户向插件传配置只能通过环境变量(env) -如果插件支持多平台,切换平台时需要修改环境变量,操作繁琐 -单平台插件更清晰,维护成本低,不易出现"代码屎山" +### 1. 鍗曞钩鍙板師鍒欙紙閲嶈锛? +**姣忎釜鎻掍欢鍙敮鎸佷竴涓钩鍙?*锛屼緥濡傦細 +- 鉁?骞冲彴A鎻掍欢锛堜粎鏀寔 A锛?- 鉁?骞冲彴B鎻掍欢锛堜粎鏀寔 B锛?- 鉂?鑱氬悎鎻掍欢锛堝悓鏃舵敮鎸?A + B + C锛? +**鍘熷洜璇存槑**锛?``` +鐢ㄦ埛鍚戞彃浠朵紶閰嶇疆鍙兘閫氳繃鐜鍙橀噺锛坋nv锛?濡傛灉鎻掍欢鏀寔澶氬钩鍙帮紝鍒囨崲骞冲彴鏃堕渶瑕佷慨鏀圭幆澧冨彉閲忥紝鎿嶄綔绻佺悙 +鍗曞钩鍙版彃浠舵洿娓呮櫚锛岀淮鎶ゆ垚鏈綆锛屼笉鏄撳嚭鐜?浠g爜灞庡北" ``` -**如需多平台支持**:建议自建后端服务,统一处理搜索和 URL 获取,前端插件只作为代理。 +**濡傞渶澶氬钩鍙版敮鎸?*锛氬缓璁嚜寤哄悗绔湇鍔★紝缁熶竴澶勭悊鎼滅储鍜?URL 鑾峰彇锛屽墠绔彃浠跺彧浣滀负浠g悊銆? +### 2. 閰嶇疆鏂瑰紡 -### 2. 配置方式 - -通过 `global.env` 读取环境变量(JSON 格式): +閫氳繃 `global.env` 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛夛細 ```javascript -// 读取用户配置的 API 密钥 +// 璇诲彇鐢ㄦ埛閰嶇疆鐨?API 瀵嗛挜 const env = global.env || {} const API_KEY = env.API_KEY || '' -// 读取自定义服务端地址 -const CUSTOM_SERVER = env.SERVER_URL || '默认地址' +// 璇诲彇鑷畾涔夋湇鍔$鍦板潃 +const CUSTOM_SERVER = env.SERVER_URL || '榛樿鍦板潃' ``` -**环境变量加载方式**: -```javascript +**鐜鍙橀噺鍔犺浇鏂瑰紡**锛?```javascript const plugin = require('./index.js') -global.env = $envCommand // 由系统注入,格式为 JSON +global.env = $envCommand // 鐢辩郴缁熸敞鍏ワ紝鏍煎紡涓?JSON -// 在插件代码中通过 global.env 读取 +// 鍦ㄦ彃浠朵唬鐮佷腑閫氳繃 global.env 璇诲彇 const env = global.env || {} const API_KEY = env.API_KEY || '' ``` -### 3. 支持的音质标识 - -| 标识 | 说明 | +### 3. 鏀寔鐨勯煶璐ㄦ爣璇? +| 鏍囪瘑 | 璇存槑 | |------|------| -| `128k` | 标准音质 | -| `320k` | 高品音质 | -| `flac` | 无损音质 | -| `flac24bit` | 高解析度无损 | -| `hires` | 超高解析度 | +| `128k` | 鏍囧噯闊宠川 | +| `320k` | 楂樺搧闊宠川 | +| `flac` | 鏃犳崯闊宠川 | +| `flac24bit` | 楂樿В鏋愬害鏃犳崯 | +| `hires` | 瓒呴珮瑙f瀽搴?| --- -## 从其他平台迁移指南 - -### 迁移对照表 - -| 其他系统 | 本系统 | 说明 | +## 浠庡叾浠栧钩鍙拌縼绉绘寚鍗? +### 杩佺Щ瀵圭収琛? +| 鍏朵粬绯荤粺 | 鏈郴缁?| 璇存槑 | |----------|--------|------| -| `globalThis.lx` | `require` 模块 | 不再需要全局对象 | -| `globalThis.lx.request` | `axios` 或 `httpFetch` | 使用标准 HTTP 库 | -| `globalThis.lx.env` | `global.env` | 环境变量读取方式 | -| `on(EVENT_NAMES.request, ...)` | 直接导出函数 | 改为函数导出 | -| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 改为模块导出 | -| `musicInfo.songmid` | `musicInfo.id` | 字段名可能不同 | -| `info.type` | `quality` 参数 | 音质参数位置 | +| `globalThis.lx` | `require` 妯″潡 | 涓嶅啀闇€瑕佸叏灞€瀵硅薄 | +| `globalThis.lx.request` | `axios` 鎴?`httpFetch` | 浣跨敤鏍囧噯 HTTP 搴?| +| `globalThis.lx.env` | `global.env` | 鐜鍙橀噺璇诲彇鏂瑰紡 | +| `on(EVENT_NAMES.request, ...)` | 鐩存帴瀵煎嚭鍑芥暟 | 鏀逛负鍑芥暟瀵煎嚭 | +| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 鏀逛负妯″潡瀵煎嚭 | +| `musicInfo.songmid` | `musicInfo.id` | 瀛楁鍚嶅彲鑳戒笉鍚?| +| `info.type` | `quality` 鍙傛暟 | 闊宠川鍙傛暟浣嶇疆 | -### 迁移步骤 +### 杩佺Щ姝ラ -#### 步骤 1:修改模块导入 - -**其他系统原代码:** +#### 姝ラ 1锛氫慨鏀规ā鍧楀鍏? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript const { EVENT_NAMES, request, on, send, env, version } = globalThis.lx ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript const axios = require('axios') const crypto = require('crypto') -// 环境变量从 global.env 读取 +// 鐜鍙橀噺浠?global.env 璇诲彇 const env = global.env || {} const API_KEY = env.API_KEY || '' ``` -#### 步骤 2:修改 HTTP 请求 +#### 姝ラ 2锛氫慨鏀?HTTP 璇锋眰 -**其他系统原代码:** +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript function httpRequest(url) { return new Promise((resolve, reject) => { @@ -143,7 +128,7 @@ function httpRequest(url) { } ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript async function httpRequest(url, options = {}) { const response = await axios({ @@ -156,11 +141,10 @@ async function httpRequest(url, options = {}) { } ``` -#### 步骤 3:修改函数导出 - -**其他系统原代码:** +#### 姝ラ 3锛氫慨鏀瑰嚱鏁板鍑? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript -// 事件监听方式 +// 浜嬩欢鐩戝惉鏂瑰紡 on(EVENT_NAMES.request, ({ action, source, info }) => { switch (action) { case 'musicUrl': @@ -168,72 +152,68 @@ on(EVENT_NAMES.request, ({ action, source, info }) => { } }) -// 初始化事件 -send(EVENT_NAMES.inited, { +// 鍒濆鍖栦簨浠?send(EVENT_NAMES.inited, { status: true, sources: musicSource }) ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript -// 直接导出函数 +// 鐩存帴瀵煎嚭鍑芥暟 module.exports = { - // 搜索功能 + // 鎼滅储鍔熻兘 musicSearch, - // 获取 URL + // 鑾峰彇 URL getUrl, - // 获取歌词 + // 鑾峰彇姝岃瘝 getLyric, - // 获取列表 + // 鑾峰彇鍒楄〃 songList, - // 获取合集 + // 鑾峰彇鍚堥泦 album, - // 获取热搜 + // 鑾峰彇鐑悳 hotSearch, - // 插件信息 + // 鎻掍欢淇℃伅 pluginInfo: { - info: { id: 'A', name: '平台A', version: '3' }, + info: { id: 'A', name: '骞冲彴A', version: '3' }, quality: [...], supportFunc: [...] } } ``` -#### 步骤 4:修改返回数据格式 - -**其他系统原代码:** +#### 姝ラ 4锛氫慨鏀硅繑鍥炴暟鎹牸寮? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript -// 直接返回 URL 字符串 -return 'https://example.com/audio.mp3' +// 鐩存帴杩斿洖 URL 瀛楃涓?return 'https://example.com/audio.mp3' -// 或返回歌词对象(其他系统格式) -return { - lyric: '[00:00.000]歌词内容', - tlyric: '[00:00.000]翻译歌词' +// 鎴栬繑鍥炴瓕璇嶅璞★紙鍏朵粬绯荤粺鏍煎紡锛?return { + lyric: '[00:00.000]姝岃瘝鍐呭', + tlyric: '[00:00.000]缈昏瘧姝岃瘝' } ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript -// 搜索返回统一格式 +// 鎼滅储杩斿洖缁熶竴鏍煎紡 return { list: [{ id: '123456', - name: '内容名', - artists: '创作者', + name: '鍐呭鍚?, + artists: '鍒涗綔鑰?, source: 'A', - pic: '封面URL', - mPic: '中封面URL', - sPic: '小封面URL', - albumName: '合集名', - albumId: '合集ID', + pic: '灏侀潰URL', + mPic: '涓皝闈RL', + sPic: '灏忓皝闈RL', + albumName: '鍚堥泦鍚?, + albumId: '鍚堥泦ID', interval: '03:45', qualities: { '128k': '4.2M', 'flac': '35M' } }], @@ -244,92 +224,79 @@ return { source: 'A' } -// 歌词返回格式(见下文歌词格式规范) -// 系统支持多种歌词格式,返回有的即可 -return { - lrc: 'LRC格式歌词', - qrc: 'QRC逐字歌词', - krc: 'KRC歌词', - ttml: 'TTML歌词', - translate: '翻译歌词' +// 姝岃瘝杩斿洖鏍煎紡锛堣涓嬫枃姝岃瘝鏍煎紡瑙勮寖锛?// 绯荤粺鏀寔澶氱姝岃瘝鏍煎紡锛岃繑鍥炴湁鐨勫嵆鍙?return { + lrc: 'LRC鏍煎紡姝岃瘝', + qrc: 'QRC閫愬瓧姝岃瘝', + krc: 'KRC姝岃瘝', + ttml: 'TTML姝岃瘝', + translate: '缈昏瘧姝岃瘝' } -// 或直接返回歌词文本字符串(系统会自动判断格式) -return '[00:00.000]歌词内容\n[00:05.000]第二行歌词...' +// 鎴栫洿鎺ヨ繑鍥炴瓕璇嶆枃鏈瓧绗︿覆锛堢郴缁熶細鑷姩鍒ゆ柇鏍煎紡锛?return '[00:00.000]姝岃瘝鍐呭\n[00:05.000]绗簩琛屾瓕璇?..' ``` --- -## 插件基本结构 +## 鎻掍欢鍩烘湰缁撴瀯 -### 核心导入 +### 鏍稿績瀵煎叆 ```javascript 'use strict' -// 标准 Node.js 模块 +// 鏍囧噯 Node.js 妯″潡 const axios = require('axios') const crypto = require('crypto') ``` -### 配置区域 +### 閰嶇疆鍖哄煙 ```javascript -// ========== 用户可配置区域 ========== +// ========== 鐢ㄦ埛鍙厤缃尯鍩?========== -// 从 global.env 读取环境变量(JSON 格式) -const env = global.env || {} +// 浠?global.env 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛?const env = global.env || {} -// 服务端地址(用户可通过环境变量覆盖) -const API_BASE = env.SERVER_URL || 'https://your-server.com' +// 鏈嶅姟绔湴鍧€锛堢敤鎴峰彲閫氳繃鐜鍙橀噺瑕嗙洊锛?const API_BASE = env.SERVER_URL || 'https://your-server.com' -// API 密钥(用户通过环境变量设置) -const API_KEY = env.API_KEY || '' +// API 瀵嗛挜锛堢敤鎴烽€氳繃鐜鍙橀噺璁剧疆锛?const API_KEY = env.API_KEY || '' -// 当前平台标识(单平台插件固定值) +// 褰撳墠骞冲彴鏍囪瘑锛堝崟骞冲彴鎻掍欢鍥哄畾鍊硷級 const PLATFORM = 'A' -// 支持的音质列表 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] +// 鏀寔鐨勯煶璐ㄥ垪琛?const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] ``` -### 导出结构 +### 瀵煎嚭缁撴瀯 ```javascript -// ========== 插件导出 ========== +// ========== 鎻掍欢瀵煎嚭 ========== module.exports = { - // 核心功能(必须实现) - musicSearch, // 内容搜索 - getUrl, // 获取 URL + // 鏍稿績鍔熻兘锛堝繀椤诲疄鐜帮級 + musicSearch, // 鍐呭鎼滅储 + getUrl, // 鑾峰彇 URL - // 可选功能 - getLyric, // 获取歌词 - songList, // 列表详情 - album, // 合集详情 - hotSearch, // 热搜词 - tipSearch, // 搜索提示 - leaderboard, // 排行榜 - - // 插件信息(必须) + // 鍙€夊姛鑳? getLyric, // 鑾峰彇姝岃瘝 + songList, // 鍒楄〃璇︽儏 + album, // 鍚堥泦璇︽儏 + hotSearch, // 鐑悳璇? tipSearch, // 鎼滅储鎻愮ず + leaderboard, // 鎺掕姒? + // 鎻掍欢淇℃伅锛堝繀椤伙級 pluginInfo: { info: { - id: 'A', // 平台标识 - name: '平台A', // 显示名称 - description: '平台A插件', // 描述 - version: '3' // 版本号 - }, - env: [ // 环境变量配置 - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + id: 'A', // 骞冲彴鏍囪瘑 + name: '骞冲彴A', // 鏄剧ず鍚嶇О + description: '骞冲彴A鎻掍欢', // 鎻忚堪 + version: '3' // 鐗堟湰鍙? }, + env: [ // 鐜鍙橀噺閰嶇疆 + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], - ext: [], // 扩展功能 - quality: [ // 支持的音质 - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' } + ext: [], // 鎵╁睍鍔熻兘 + quality: [ // 鏀寔鐨勯煶璐? { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' } ], - supportFunc: [ // 支持的功能 - 'search_song', + supportFunc: [ // 鏀寔鐨勫姛鑳? 'search_song', 'search_playlist', 'playlist', 'album', @@ -341,143 +308,124 @@ module.exports = { --- -## 数据格式规范 +## 鏁版嵁鏍煎紡瑙勮寖 -### 搜索结果统一格式 +### 鎼滅储缁撴灉缁熶竴鏍煎紡 ```javascript { list: [ { - id: String, // 唯一标识 - name: String, // 名称 - artists: String, // 创作者(用 "、" 分隔) - source: String, // 平台标识 - pic: String, // 大封面 URL - mPic: String, // 中封面 URL - sPic: String, // 小封面 URL - albumName: String, // 合集名 - albumId: String, // 合集 ID - interval: String, // 时长 "mm:ss" - qualities: { // 音质 -> 文件大小 + id: String, // 鍞竴鏍囪瘑 + name: String, // 鍚嶇О + artists: String, // 鍒涗綔鑰咃紙鐢?"銆? 鍒嗛殧锛? source: String, // 骞冲彴鏍囪瘑 + pic: String, // 澶у皝闈?URL + mPic: String, // 涓皝闈?URL + sPic: String, // 灏忓皝闈?URL + albumName: String, // 鍚堥泦鍚? albumId: String, // 鍚堥泦 ID + interval: String, // 鏃堕暱 "mm:ss" + qualities: { // 闊宠川 -> 鏂囦欢澶у皬 '128k': '4.2M', '320k': '8.5M', 'flac': '35M' } } ], - total: Number, // 总数量 - page: Number, // 当前页码 - limit: Number, // 每页数量 - allPage: Number, // 总页数 - source: String // 平台标识 + total: Number, // 鎬绘暟閲? page: Number, // 褰撳墠椤电爜 + limit: Number, // 姣忛〉鏁伴噺 + allPage: Number, // 鎬婚〉鏁? source: String // 骞冲彴鏍囪瘑 } ``` -### 歌词返回格式 - -**重要说明**:系统会自动判断歌词格式(lrc、qrc、krc、ttml 等),插件只需返回对应的歌词字段即可。 +### 姝岃瘝杩斿洖鏍煎紡 +**閲嶈璇存槑**锛氱郴缁熶細鑷姩鍒ゆ柇姝岃瘝鏍煎紡锛坙rc銆乹rc銆乲rc銆乼tml 绛夛級锛屾彃浠跺彧闇€杩斿洖瀵瑰簲鐨勬瓕璇嶅瓧娈靛嵆鍙€? ```javascript -// 推荐返回格式 +// 鎺ㄨ崘杩斿洖鏍煎紡 { - lrc: String, // LRC 格式歌词 - qrc: String, // QRC 逐字歌词 - krc: String, // KRC 歌词 - ttml: String, // TTML 歌词 - translate: String // 翻译歌词 + lrc: String, // LRC 鏍煎紡姝岃瘝 + qrc: String, // QRC 閫愬瓧姝岃瘝 + krc: String, // KRC 姝岃瘝 + ttml: String, // TTML 姝岃瘝 + translate: String // 缈昏瘧姝岃瘝 } -// 或者直接返回歌词文本字符串(系统会自动判断格式) -return '[00:00.000]歌词内容\n[00:05.000]第二行歌词...' +// 鎴栬€呯洿鎺ヨ繑鍥炴瓕璇嶆枃鏈瓧绗︿覆锛堢郴缁熶細鑷姩鍒ゆ柇鏍煎紡锛?return '[00:00.000]姝岃瘝鍐呭\n[00:05.000]绗簩琛屾瓕璇?..' ``` -**字段说明**: - -| 字段 | 说明 | 格式 | +**瀛楁璇存槑**锛? +| 瀛楁 | 璇存槑 | 鏍煎紡 | |------|------|------| -| `lrc` | 标准 LRC 歌词 | `[mm:ss.ms]歌词内容` | -| `qrc` | 逐字歌词 | QRC 格式(Base64 编码) | -| `krc` | KRC 歌词 | KRC 格式 | -| `ttml` | TTML 歌词 | TTML/XML 格式 | -| `translate` | 翻译歌词 | LRC 格式或纯文本 | +| `lrc` | 鏍囧噯 LRC 姝岃瘝 | `[mm:ss.ms]姝岃瘝鍐呭` | +| `qrc` | 閫愬瓧姝岃瘝 | QRC 鏍煎紡锛圔ase64 缂栫爜锛?| +| `krc` | KRC 姝岃瘝 | KRC 鏍煎紡 | +| `ttml` | TTML 姝岃瘝 | TTML/XML 鏍煎紡 | +| `translate` | 缈昏瘧姝岃瘝 | LRC 鏍煎紡鎴栫函鏂囨湰 | -**注意事项**: -- 以上字段均为可选,返回有的即可 -- 系统会自动识别歌词格式 -- `translate` 用于翻译歌词 -- 直接返回歌词文本字符串也是允许的,系统会尝试自动判断其格式 -- 不同平台可能返回不同格式,如:平台A可能返回 `qrc` + `translate`,平台B可能返回 `lrc` + `tlyric` +**娉ㄦ剰浜嬮」**锛?- 浠ヤ笂瀛楁鍧囦负鍙€夛紝杩斿洖鏈夌殑鍗冲彲 +- 绯荤粺浼氳嚜鍔ㄨ瘑鍒瓕璇嶆牸寮?- `translate` 鐢ㄤ簬缈昏瘧姝岃瘝 +- 鐩存帴杩斿洖姝岃瘝鏂囨湰瀛楃涓蹭篃鏄厑璁哥殑锛岀郴缁熶細灏濊瘯鑷姩鍒ゆ柇鍏舵牸寮?- 涓嶅悓骞冲彴鍙兘杩斿洖涓嶅悓鏍煎紡锛屽锛氬钩鍙癆鍙兘杩斿洖 `qrc` + `translate`锛屽钩鍙癇鍙兘杩斿洖 `lrc` + `tlyric` -### 列表详情统一格式 +### 鍒楄〃璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 内容列表,格式同搜索结果 */], + list: [/* 鍐呭鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 列表名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 创建者 - } + name: String, // 鍒楄〃鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 鍒涘缓鑰? } } ``` -### 合集详情统一格式 +### 鍚堥泦璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 内容列表,格式同搜索结果 */], + list: [/* 鍐呭鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 合集名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 创作者 - } + name: String, // 鍚堥泦鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 鍒涗綔鑰? } } ``` --- -## 示例代码详解 +## 绀轰緥浠g爜璇﹁В -### 完整插件模板 +### 瀹屾暣鎻掍欢妯℃澘 ```javascript 'use strict' -// ==================== 核心导入 ==================== +// ==================== 鏍稿績瀵煎叆 ==================== const axios = require('axios') const crypto = require('crypto') -// ==================== 配置区域 ==================== +// ==================== 閰嶇疆鍖哄煙 ==================== -// 从 global.env 读取环境变量(JSON 格式) -const env = global.env || {} +// 浠?global.env 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛?const env = global.env || {} -// 平台标识(固定值,单平台插件) +// 骞冲彴鏍囪瘑锛堝浐瀹氬€硷紝鍗曞钩鍙版彃浠讹級 const PLATFORM = 'A' -// 服务端配置 -const CONFIG = { +// 鏈嶅姟绔厤缃?const CONFIG = { serverUrl: env.SERVER_URL || 'https://api.example.com', apiKey: env.API_KEY || '', timeout: 10000 } -// 支持的音质 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] +// 鏀寔鐨勯煶璐?const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] -// ==================== 工具函数 ==================== +// ==================== 宸ュ叿鍑芥暟 ==================== function sizeFormate(size) { if (!size || isNaN(size)) return '' @@ -496,7 +444,7 @@ function formatPlayTime(time) { function formatArtistName(artistList) { if (!artistList || !Array.isArray(artistList)) return '' - return artistList.map(a => a.name || a).join('、') + return artistList.map(a => a.name || a).join('銆?) } function decodeName(str) { @@ -510,7 +458,7 @@ function decodeName(str) { .replace(/ /g, ' ') } -// ==================== 核心功能 ==================== +// ==================== 鏍稿績鍔熻兘 ==================== async function musicSearch(str, page = 1, limit = 20) { const params = { keyword: str, page, limit } @@ -567,7 +515,7 @@ async function getUrl(id, quality) { const data = response.data if (!data.url || !data.url.startsWith('http')) { - throw new Error('获取链接失败') + throw new Error('鑾峰彇閾炬帴澶辫触') } return data.url @@ -583,9 +531,8 @@ async function getLyric(id) { const data = response.data - // 返回歌词对象,系统会自动判断格式 - // 返回有的字段即可,不需要全部字段 - return { + // 杩斿洖姝岃瘝瀵硅薄锛岀郴缁熶細鑷姩鍒ゆ柇鏍煎紡 + // 杩斿洖鏈夌殑瀛楁鍗冲彲锛屼笉闇€瑕佸叏閮ㄥ瓧娈? return { lrc: data.lrc || '', qrc: data.qrc || '', krc: data.krc || '', @@ -593,8 +540,7 @@ async function getLyric(id) { translate: data.translate || '' } - // 或者直接返回歌词文本 - // return data.lyric + // 鎴栬€呯洿鎺ヨ繑鍥炴瓕璇嶆枃鏈? // return data.lyric } async function songList(id, page = 1, limit = 20) { @@ -688,7 +634,7 @@ async function hotSearch() { } } -// ==================== 插件导出 ==================== +// ==================== 鎻掍欢瀵煎嚭 ==================== module.exports = { musicSearch, @@ -701,19 +647,19 @@ module.exports = { pluginInfo: { info: { id: PLATFORM, - name: '平台A', - description: '平台A插件', + name: '骞冲彴A', + description: '骞冲彴A鎻掍欢', version: '3' }, env: [ - { key: 'SERVER_URL', name: '服务端地址', description: '自定义服务端地址' }, - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + { key: 'SERVER_URL', name: '鏈嶅姟绔湴鍧€', description: '鑷畾涔夋湇鍔$鍦板潃' }, + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], ext: [], quality: [ - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' } + { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' } ], supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] } @@ -722,21 +668,19 @@ module.exports = { --- -## 平台配置参考 +## 骞冲彴閰嶇疆鍙傝€? +### 鍚勫钩鍙版爣璇嗗鐓ц〃 -### 各平台标识对照表 - -| 平台 | 标识 | 常见 ID 字段 | +| 骞冲彴 | 鏍囪瘑 | 甯歌 ID 瀛楁 | |------|------|-------------| -| 平台A | `A` | `id` | -| 平台B | `B` | `id` | -| 平台C | `C` | `id`, `hash` | -| 平台D | `D` | `hash`, `id` | -| 平台E | `E` | `id` | -| 平台F | `F` | `id` | - -### 音质支持参考 +| 骞冲彴A | `A` | `id` | +| 骞冲彴B | `B` | `id` | +| 骞冲彴C | `C` | `id`, `hash` | +| 骞冲彴D | `D` | `hash`, `id` | +| 骞冲彴E | `E` | `id` | +| 骞冲彴F | `F` | `id` | +### 闊宠川鏀寔鍙傝€? ```javascript const PLATFORM_QUALITIES = { A: ['128k', '320k', 'flac', 'flac24bit', 'hires'], @@ -750,113 +694,95 @@ const PLATFORM_QUALITIES = { --- -## 开发建议 - -### 多文件开发 - -**推荐使用多文件开发模式**,将不同功能模块分离,防止代码堆叠: +## 寮€鍙戝缓璁? +### 澶氭枃浠跺紑鍙? +**鎺ㄨ崘浣跨敤澶氭枃浠跺紑鍙戞ā寮?*锛屽皢涓嶅悓鍔熻兘妯″潡鍒嗙锛岄槻姝唬鐮佸爢鍙狅細 ``` plugin/ -├── index.js # 主入口,导出模块 -├── search.js # 搜索相关功能 -├── lyric.js # 歌词相关功能 -├── playlist.js # 列表相关功能 -├── utils.js # 工具函数 -└── config.js # 配置常量 +鈹溾攢鈹€ index.js # 涓诲叆鍙o紝瀵煎嚭妯″潡 +鈹溾攢鈹€ search.js # 鎼滅储鐩稿叧鍔熻兘 +鈹溾攢鈹€ lyric.js # 姝岃瘝鐩稿叧鍔熻兘 +鈹溾攢鈹€ playlist.js # 鍒楄〃鐩稿叧鍔熻兘 +鈹溾攢鈹€ utils.js # 宸ュ叿鍑芥暟 +鈹斺攢鈹€ config.js # 閰嶇疆甯搁噺 ``` -### 使用 ncc 打包 - -开发完成后,使用 `ncc` 将多文件打包为单文件即可: +### 浣跨敤 ncc 鎵撳寘 +寮€鍙戝畬鎴愬悗锛屼娇鐢?`ncc` 灏嗗鏂囦欢鎵撳寘涓哄崟鏂囦欢鍗冲彲锛? ```bash -# 安装 ncc +# 瀹夎 ncc npm install -g @vercel/ncc -# 打包 +# 鎵撳寘 ncc build index.js -o dist -# 输出的 dist/index.js 即为最终插件文件 -``` - -**ncc 打包优点**: -- 将多文件合并为单文件,便于分发 -- 自动处理依赖关系 -- 保持 CommonJS 兼容性 +# 杈撳嚭鐨?dist/index.js 鍗充负鏈€缁堟彃浠舵枃浠?``` +**ncc 鎵撳寘浼樼偣**锛?- 灏嗗鏂囦欢鍚堝苟涓哄崟鏂囦欢锛屼究浜庡垎鍙?- 鑷姩澶勭悊渚濊禆鍏崇郴 +- 淇濇寔 CommonJS 鍏煎鎬? --- -## 常见问题 +## 甯歌闂 -### Q1: 如何从其他系统迁移到本系统? +### Q1: 濡備綍浠庡叾浠栫郴缁熻縼绉诲埌鏈郴缁燂紵 -**A**: 参考本文档的"从其他平台迁移指南"章节,主要修改点: -1. 将 `globalThis.lx.request` 改为 `axios` -2. 将事件监听改为函数导出 -3. 修改数据返回格式 -4. 添加 `module.exports` 导出 -5. 环境变量从 `global.env` 读取 +**A**: 鍙傝€冩湰鏂囨。鐨?浠庡叾浠栧钩鍙拌縼绉绘寚鍗?绔犺妭锛屼富瑕佷慨鏀圭偣锛?1. 灏?`globalThis.lx.request` 鏀逛负 `axios` +2. 灏嗕簨浠剁洃鍚敼涓哄嚱鏁板鍑?3. 淇敼鏁版嵁杩斿洖鏍煎紡 +4. 娣诲姞 `module.exports` 瀵煎嚭 +5. 鐜鍙橀噺浠?`global.env` 璇诲彇 -### Q2: 插件加载失败怎么办? +### Q2: 鎻掍欢鍔犺浇澶辫触鎬庝箞鍔烇紵 -**A**: 检查以下几点: -1. 文件语法是否正确:`node -c your-plugin.js` -2. `pluginInfo` 是否完整 -3. 导出的函数名是否正确 -4. 依赖模块是否已安装(如 `axios`) +**A**: 妫€鏌ヤ互涓嬪嚑鐐癸細 +1. 鏂囦欢璇硶鏄惁姝g‘锛歚node -c your-plugin.js` +2. `pluginInfo` 鏄惁瀹屾暣 +3. 瀵煎嚭鐨勫嚱鏁板悕鏄惁姝g‘ +4. 渚濊禆妯″潡鏄惁宸插畨瑁咃紙濡?`axios`锛? +### Q3: 鎼滅储杩斿洖绌虹粨鏋滐紵 -### Q3: 搜索返回空结果? - -**A**: 检查: -1. API 请求是否成功(查看日志) -2. 响应数据解析是否正确 -3. 数据格式是否符合规范 -4. 返回的 `source` 是否与平台标识一致 - -### Q4: 播放失败? - -**A**: 检查: -1. `getUrl` 返回的 URL 是否有效 -2. URL 是否以 `http` 开头 -3. 音质标识是否正确 -4. 是否有跨域或权限问题 - -### Q5: 用户如何配置插件? - -**A**: 用户通过设置界面配置环境变量: +**A**: 妫€鏌ワ細 +1. API 璇锋眰鏄惁鎴愬姛锛堟煡鐪嬫棩蹇楋級 +2. 鍝嶅簲鏁版嵁瑙f瀽鏄惁姝g‘ +3. 鏁版嵁鏍煎紡鏄惁绗﹀悎瑙勮寖 +4. 杩斿洖鐨?`source` 鏄惁涓庡钩鍙版爣璇嗕竴鑷? +### Q4: 鎾斁澶辫触锛? +**A**: 妫€鏌ワ細 +1. `getUrl` 杩斿洖鐨?URL 鏄惁鏈夋晥 +2. URL 鏄惁浠?`http` 寮€澶?3. 闊宠川鏍囪瘑鏄惁姝g‘ +4. 鏄惁鏈夎法鍩熸垨鏉冮檺闂 +### Q5: 鐢ㄦ埛濡備綍閰嶇疆鎻掍欢锛? +**A**: 鐢ㄦ埛閫氳繃璁剧疆鐣岄潰閰嶇疆鐜鍙橀噺锛? ``` -SERVER_URL = 自定义服务端地址 -API_KEY = 用户的API密钥 +SERVER_URL = 鑷畾涔夋湇鍔$鍦板潃 +API_KEY = 鐢ㄦ埛鐨凙PI瀵嗛挜 ``` -插件通过 `global.env` 读取: - +鎻掍欢閫氳繃 `global.env` 璇诲彇锛? ```javascript const env = global.env || {} -const SERVER_URL = env.SERVER_URL || '默认地址' +const SERVER_URL = env.SERVER_URL || '榛樿鍦板潃' ``` -### Q6: 歌词格式应该返回什么? +### Q6: 姝岃瘝鏍煎紡搴旇杩斿洖浠€涔堬紵 -**A**: 返回包含 `lrc`、`qrc`、`krc`、`ttml`、`translate` 等字段的对象,或直接返回歌词文本字符串。系统会自动判断歌词格式。 +**A**: 杩斿洖鍖呭惈 `lrc`銆乣qrc`銆乣krc`銆乣ttml`銆乣translate` 绛夊瓧娈电殑瀵硅薄锛屾垨鐩存帴杩斿洖姝岃瘝鏂囨湰瀛楃涓层€傜郴缁熶細鑷姩鍒ゆ柇姝岃瘝鏍煎紡銆? +--- + +## 闄勫綍 + +### 鍙傝€冭祫婧? +- Node.js 瀹樻柟鏂囨。 +- axios 鏂囨。 +- @vercel/ncc 鎵撳寘宸ュ叿 + +### 鐗堟湰鍘嗗彶 + +- v1.0.3: 褰撳墠鐗堟湰 +- v1.0.0: 鍒濆鐗堟湰 --- -## 附录 - -### 参考资源 - -- Node.js 官方文档 -- axios 文档 -- @vercel/ncc 打包工具 - -### 版本历史 - -- v1.0.3: 当前版本 -- v1.0.0: 初始版本 - ---- - -**文档结束** +**鏂囨。缁撴潫** diff --git a/QZ_Music-V2 插件规范(初版)│QZ_Music-V2-Plugin-Development-Guide.md b/QZ_Music-V2 插件规范(初版)│QZ_Music-V2-Plugin-Development-Guide.md index 4b0a3e8..11a92c8 100644 --- a/QZ_Music-V2 插件规范(初版)│QZ_Music-V2-Plugin-Development-Guide.md +++ b/QZ_Music-V2 插件规范(初版)│QZ_Music-V2-Plugin-Development-Guide.md @@ -1,130 +1,115 @@ -# 插件开发帮助文档 - -## 目录 -1. [概述](#概述) -2. [核心设计原则](#核心设计原则) -3. [从其他平台迁移指南](#从其他平台迁移指南) -4. [插件基本结构](#插件基本结构) -5. [数据格式规范](#数据格式规范) -6. [示例代码详解](#示例代码详解) -7. [平台配置参考](#平台配置参考) -8. [常见问题](#常见问题) +# 鎻掍欢寮€鍙戝府鍔╂枃妗? +## 鐩綍 +1. [姒傝堪](#姒傝堪) +2. [鏍稿績璁捐鍘熷垯](#鏍稿績璁捐鍘熷垯) +3. [浠庡叾浠栧钩鍙拌縼绉绘寚鍗梋(#浠庡叾浠栧钩鍙拌縼绉绘寚鍗? +4. [鎻掍欢鍩烘湰缁撴瀯](#鎻掍欢鍩烘湰缁撴瀯) +5. [鏁版嵁鏍煎紡瑙勮寖](#鏁版嵁鏍煎紡瑙勮寖) +6. [绀轰緥浠g爜璇﹁В](#绀轰緥浠g爜璇﹁В) +7. [骞冲彴閰嶇疆鍙傝€僝(#骞冲彴閰嶇疆鍙傝€? +8. [甯歌闂](#甯歌闂) --- -## 概述 +## 姒傝堪 -本文档用于指导开发者编写音源插件。本系统使用 Node.js 运行时环境,插件采用 CommonJS 模块规范,通过 `module.exports` 导出功能接口。 +鏈枃妗g敤浜庢寚瀵煎紑鍙戣€呯紪鍐欓煶婧愭彃浠躲€傛湰绯荤粺浣跨敤 Node.js 杩愯鏃剁幆澧冿紝鎻掍欢閲囩敤 CommonJS 妯″潡瑙勮寖锛岄€氳繃 `module.exports` 瀵煎嚭鍔熻兘鎺ュ彛銆? +### 涓庡叾浠栫郴缁熺殑鍖哄埆 -### 与其他系统的区别 - -| 特性 | 本系统 | 其他系统 | +| 鐗规€?| 鏈郴缁?| 鍏朵粬绯荤粺 | |------|--------|----------| -| 运行时 | Node.js | JavaScript 运行时 | -| 模块规范 | CommonJS | 全局事件监听 | -| 导出方式 | `module.exports` | 事件发送 | -| 通信方式 | 直接函数调用 | 事件驱动 | -| HTTP 请求 | `axios` 或内置 `httpFetch` | 全局请求对象 | +| 杩愯鏃?| Node.js | JavaScript 杩愯鏃?| +| 妯″潡瑙勮寖 | CommonJS | 鍏ㄥ眬浜嬩欢鐩戝惉 | +| 瀵煎嚭鏂瑰紡 | `module.exports` | 浜嬩欢鍙戦€?| +| 閫氫俊鏂瑰紡 | 鐩存帴鍑芥暟璋冪敤 | 浜嬩欢椹卞姩 | +| HTTP 璇锋眰 | `axios` 鎴栧唴缃?`httpFetch` | 鍏ㄥ眬璇锋眰瀵硅薄 | -### 插件类型 - -本系统插件属于**完整音源插件**,核心功能包括: -- 搜索内容 -- 获取播放 URL -- 获取歌词 -- 获取列表/合集信息 -- 获取热搜/排行榜 +### 鎻掍欢绫诲瀷 +鏈郴缁熸彃浠跺睘浜?*瀹屾暣闊虫簮鎻掍欢**锛屾牳蹇冨姛鑳藉寘鎷細 +- 鎼滅储鍐呭 +- 鑾峰彇鎾斁 URL +- 鑾峰彇姝岃瘝 +- 鑾峰彇鍒楄〃/鍚堥泦淇℃伅 +- 鑾峰彇鐑悳/鎺掕姒? --- -## 核心设计原则 +## 鏍稿績璁捐鍘熷垯 -### 1. 单平台原则(重要) - -**每个插件只支持一个平台**,例如: -- ✅ 平台A插件(仅支持 A) -- ✅ 平台B插件(仅支持 B) -- ❌ 聚合插件(同时支持 A + B + C) - -**原因说明**: -``` -用户向插件传配置只能通过环境变量(env) -如果插件支持多平台,切换平台时需要修改环境变量,操作繁琐 -单平台插件更清晰,维护成本低,不易出现"代码屎山" +### 1. 鍗曞钩鍙板師鍒欙紙閲嶈锛? +**姣忎釜鎻掍欢鍙敮鎸佷竴涓钩鍙?*锛屼緥濡傦細 +- 鉁?骞冲彴A鎻掍欢锛堜粎鏀寔 A锛?- 鉁?骞冲彴B鎻掍欢锛堜粎鏀寔 B锛?- 鉂?鑱氬悎鎻掍欢锛堝悓鏃舵敮鎸?A + B + C锛? +**鍘熷洜璇存槑**锛?``` +鐢ㄦ埛鍚戞彃浠朵紶閰嶇疆鍙兘閫氳繃鐜鍙橀噺锛坋nv锛?濡傛灉鎻掍欢鏀寔澶氬钩鍙帮紝鍒囨崲骞冲彴鏃堕渶瑕佷慨鏀圭幆澧冨彉閲忥紝鎿嶄綔绻佺悙 +鍗曞钩鍙版彃浠舵洿娓呮櫚锛岀淮鎶ゆ垚鏈綆锛屼笉鏄撳嚭鐜?浠g爜灞庡北" ``` -**如需多平台支持**:建议自建后端服务,统一处理搜索和 URL 获取,前端插件只作为代理。 +**濡傞渶澶氬钩鍙版敮鎸?*锛氬缓璁嚜寤哄悗绔湇鍔★紝缁熶竴澶勭悊鎼滅储鍜?URL 鑾峰彇锛屽墠绔彃浠跺彧浣滀负浠g悊銆? +### 2. 閰嶇疆鏂瑰紡 -### 2. 配置方式 - -通过 `global.env` 读取环境变量(JSON 格式): +閫氳繃 `global.env` 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛夛細 ```javascript -// 读取用户配置的 API 密钥 +// 璇诲彇鐢ㄦ埛閰嶇疆鐨?API 瀵嗛挜 const API_KEY = global.env.API_KEY || '' -// 读取自定义服务端地址 -const CUSTOM_SERVER = global.env.SERVER_URL || '默认地址' +// 璇诲彇鑷畾涔夋湇鍔$鍦板潃 +const CUSTOM_SERVER = global.env.SERVER_URL || '榛樿鍦板潃' ``` -**环境变量加载方式**: -```javascript +**鐜鍙橀噺鍔犺浇鏂瑰紡**锛?```javascript const plugin = require('./index.js') -global.env = $envCommand // 由系统注入,格式为 JSON +global.env = $envCommand // 鐢辩郴缁熸敞鍏ワ紝鏍煎紡涓?JSON -// 在插件代码中通过 global.env 读取 +// 鍦ㄦ彃浠朵唬鐮佷腑閫氳繃 global.env 璇诲彇 const env = global.env || {} const API_KEY = env.API_KEY || '' ``` -### 3. 支持的音质标识 - -| 标识 | 说明 | +### 3. 鏀寔鐨勯煶璐ㄦ爣璇? +| 鏍囪瘑 | 璇存槑 | |------|------| -| `128k` | 标准音质 | -| `320k` | 高品音质 | -| `flac` | 无损音质 | -| `flac24bit` | 高解析度无损 | -| `hires` | 超高解析度 | +| `128k` | 鏍囧噯闊宠川 | +| `320k` | 楂樺搧闊宠川 | +| `flac` | 鏃犳崯闊宠川 | +| `flac24bit` | 楂樿В鏋愬害鏃犳崯 | +| `hires` | 瓒呴珮瑙f瀽搴?| --- -## 从其他平台迁移指南 - -### 迁移对照表 - -| 其他系统 | 本系统 | 说明 | +## 浠庡叾浠栧钩鍙拌縼绉绘寚鍗? +### 杩佺Щ瀵圭収琛? +| 鍏朵粬绯荤粺 | 鏈郴缁?| 璇存槑 | |----------|--------|------| -| `globalThis.lx` | `require` 模块 | 不再需要全局对象 | -| `globalThis.lx.request` | `axios` 或 `httpFetch` | 使用标准 HTTP 库 | -| `globalThis.lx.env` | `global.env` | 环境变量读取方式 | -| `on(EVENT_NAMES.request, ...)` | 直接导出函数 | 改为函数导出 | -| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 改为模块导出 | -| `musicInfo.songmid` | `musicInfo.id` | 字段名可能不同 | -| `info.type` | `quality` 参数 | 音质参数位置 | +| `globalThis.lx` | `require` 妯″潡 | 涓嶅啀闇€瑕佸叏灞€瀵硅薄 | +| `globalThis.lx.request` | `axios` 鎴?`httpFetch` | 浣跨敤鏍囧噯 HTTP 搴?| +| `globalThis.lx.env` | `global.env` | 鐜鍙橀噺璇诲彇鏂瑰紡 | +| `on(EVENT_NAMES.request, ...)` | 鐩存帴瀵煎嚭鍑芥暟 | 鏀逛负鍑芥暟瀵煎嚭 | +| `send(EVENT_NAMES.inited, ...)` | `module.exports` | 鏀逛负妯″潡瀵煎嚭 | +| `musicInfo.songmid` | `musicInfo.id` | 瀛楁鍚嶅彲鑳戒笉鍚?| +| `info.type` | `quality` 鍙傛暟 | 闊宠川鍙傛暟浣嶇疆 | -### 迁移步骤 +### 杩佺Щ姝ラ -#### 步骤 1:修改模块导入 - -**其他系统原代码:** +#### 姝ラ 1锛氫慨鏀规ā鍧楀鍏? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript const { EVENT_NAMES, request, on, send, env, version } = globalThis.lx ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript const axios = require('axios') const crypto = require('crypto') -// 环境变量从 global.env 读取 +// 鐜鍙橀噺浠?global.env 璇诲彇 const env = global.env || {} const API_KEY = env.API_KEY || '' ``` -#### 步骤 2:修改 HTTP 请求 +#### 姝ラ 2锛氫慨鏀?HTTP 璇锋眰 -**其他系统原代码:** +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript function httpRequest(url) { return new Promise((resolve, reject) => { @@ -136,7 +121,7 @@ function httpRequest(url) { } ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript async function httpRequest(url, options = {}) { const response = await axios({ @@ -149,11 +134,10 @@ async function httpRequest(url, options = {}) { } ``` -#### 步骤 3:修改函数导出 - -**其他系统原代码:** +#### 姝ラ 3锛氫慨鏀瑰嚱鏁板鍑? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript -// 事件监听方式 +// 浜嬩欢鐩戝惉鏂瑰紡 on(EVENT_NAMES.request, ({ action, source, info }) => { switch (action) { case 'musicUrl': @@ -161,72 +145,68 @@ on(EVENT_NAMES.request, ({ action, source, info }) => { } }) -// 初始化事件 -send(EVENT_NAMES.inited, { +// 鍒濆鍖栦簨浠?send(EVENT_NAMES.inited, { status: true, sources: musicSource }) ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript -// 直接导出函数 +// 鐩存帴瀵煎嚭鍑芥暟 module.exports = { - // 搜索功能 + // 鎼滅储鍔熻兘 musicSearch, - // 获取 URL + // 鑾峰彇 URL getUrl, - // 获取歌词 + // 鑾峰彇姝岃瘝 getLyric, - // 获取列表 + // 鑾峰彇鍒楄〃 songList, - // 获取合集 + // 鑾峰彇鍚堥泦 album, - // 获取热搜 + // 鑾峰彇鐑悳 hotSearch, - // 插件信息 + // 鎻掍欢淇℃伅 pluginInfo: { - info: { id: 'A', name: '平台A', version: '3' }, + info: { id: 'A', name: '骞冲彴A', version: '3' }, quality: [...], supportFunc: [...] } } ``` -#### 步骤 4:修改返回数据格式 - -**其他系统原代码:** +#### 姝ラ 4锛氫慨鏀硅繑鍥炴暟鎹牸寮? +**鍏朵粬绯荤粺鍘熶唬鐮侊細** ```javascript -// 直接返回 URL 字符串 -return 'https://example.com/audio.mp3' +// 鐩存帴杩斿洖 URL 瀛楃涓?return 'https://example.com/audio.mp3' -// 或返回歌词对象 -return { - lyric: '[00:00.000]歌词内容', - tlyric: '[00:00.000]翻译歌词' +// 鎴栬繑鍥炴瓕璇嶅璞?return { + lyric: '[00:00.000]姝岃瘝鍐呭', + tlyric: '[00:00.000]缈昏瘧姝岃瘝' } ``` -**本系统新代码:** +**鏈郴缁熸柊浠g爜锛?* ```javascript -// 搜索返回统一格式 +// 鎼滅储杩斿洖缁熶竴鏍煎紡 return { list: [{ id: '123456', - name: '内容名', - artists: '创作者', + name: '鍐呭鍚?, + artists: '鍒涗綔鑰?, source: 'A', - pic: '封面URL', - mPic: '中封面URL', - sPic: '小封面URL', - albumName: '合集名', - albumId: '合集ID', + pic: '灏侀潰URL', + mPic: '涓皝闈RL', + sPic: '灏忓皝闈RL', + albumName: '鍚堥泦鍚?, + albumId: '鍚堥泦ID', interval: '03:45', qualities: { '128k': '4.2M', 'flac': '35M' } }], @@ -237,101 +217,90 @@ return { source: 'A' } -// 歌词返回格式 +// 姝岃瘝杩斿洖鏍煎紡 return { - lyric: '歌词内容', - tlyric: '翻译歌词' + lyric: '姝岃瘝鍐呭', + tlyric: '缈昏瘧姝岃瘝' } ``` --- -## 插件基本结构 +## 鎻掍欢鍩烘湰缁撴瀯 -### 文件头部注释 +### 鏂囦欢澶撮儴娉ㄩ噴 ```javascript /** - * @name 平台A音源 - * @description 音源插件 + * @name 骞冲彴A闊虫簮 + * @description 闊虫簮鎻掍欢 * @version 3.0.0 - * @author 开发者 - * @homepage https://github.com/your-repo + * @author 寮€鍙戣€? * @homepage https://github.com/your-repo * @license MIT * - * 支持平台: 平台A - * 支持音质: 128k, 320k, flac + * 鏀寔骞冲彴: 骞冲彴A + * 鏀寔闊宠川: 128k, 320k, flac */ ``` -### 核心导入 +### 鏍稿績瀵煎叆 ```javascript 'use strict' -// 标准 Node.js 模块 +// 鏍囧噯 Node.js 妯″潡 const axios = require('axios') const crypto = require('crypto') ``` -### 配置区域 +### 閰嶇疆鍖哄煙 ```javascript -// ========== 用户可配置区域 ========== +// ========== 鐢ㄦ埛鍙厤缃尯鍩?========== -// 从 global.env 读取环境变量(JSON 格式) -const env = global.env || {} +// 浠?global.env 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛?const env = global.env || {} -// 服务端地址(用户可通过环境变量覆盖) -const API_BASE = env.SERVER_URL || 'https://your-server.com' +// 鏈嶅姟绔湴鍧€锛堢敤鎴峰彲閫氳繃鐜鍙橀噺瑕嗙洊锛?const API_BASE = env.SERVER_URL || 'https://your-server.com' -// API 密钥(用户通过环境变量设置) -const API_KEY = env.API_KEY || '' +// API 瀵嗛挜锛堢敤鎴烽€氳繃鐜鍙橀噺璁剧疆锛?const API_KEY = env.API_KEY || '' -// 当前平台标识(单平台插件固定值) +// 褰撳墠骞冲彴鏍囪瘑锛堝崟骞冲彴鎻掍欢鍥哄畾鍊硷級 const PLATFORM = 'A' -// 支持的音质列表 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] +// 鏀寔鐨勯煶璐ㄥ垪琛?const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] ``` -### 导出结构 +### 瀵煎嚭缁撴瀯 ```javascript -// ========== 插件导出 ========== +// ========== 鎻掍欢瀵煎嚭 ========== module.exports = { - // 核心功能(必须实现) - musicSearch, // 内容搜索 - getUrl, // 获取 URL + // 鏍稿績鍔熻兘锛堝繀椤诲疄鐜帮級 + musicSearch, // 鍐呭鎼滅储 + getUrl, // 鑾峰彇 URL - // 可选功能 - getLyric, // 获取歌词 - songList, // 列表详情 - album, // 合集详情 - hotSearch, // 热搜词 - tipSearch, // 搜索提示 - leaderboard, // 排行榜 - - // 插件信息(必须) + // 鍙€夊姛鑳? getLyric, // 鑾峰彇姝岃瘝 + songList, // 鍒楄〃璇︽儏 + album, // 鍚堥泦璇︽儏 + hotSearch, // 鐑悳璇? tipSearch, // 鎼滅储鎻愮ず + leaderboard, // 鎺掕姒? + // 鎻掍欢淇℃伅锛堝繀椤伙級 pluginInfo: { info: { - id: 'A', // 平台标识 - name: '平台A', // 显示名称 - description: '平台A插件', // 描述 - version: '3' // 版本号 - }, - env: [ // 环境变量配置 - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + id: 'A', // 骞冲彴鏍囪瘑 + name: '骞冲彴A', // 鏄剧ず鍚嶇О + description: '骞冲彴A鎻掍欢', // 鎻忚堪 + version: '3' // 鐗堟湰鍙? }, + env: [ // 鐜鍙橀噺閰嶇疆 + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], - ext: [], // 扩展功能 - quality: [ // 支持的音质 - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' } + ext: [], // 鎵╁睍鍔熻兘 + quality: [ // 鏀寔鐨勯煶璐? { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' } ], - supportFunc: [ // 支持的功能 - 'search_song', + supportFunc: [ // 鏀寔鐨勫姛鑳? 'search_song', 'search_playlist', 'playlist', 'album', @@ -343,136 +312,119 @@ module.exports = { --- -## 数据格式规范 +## 鏁版嵁鏍煎紡瑙勮寖 -### 搜索结果统一格式 +### 鎼滅储缁撴灉缁熶竴鏍煎紡 ```javascript { list: [ { - id: String, // 唯一标识 - name: String, // 名称 - artists: String, // 创作者(用 "、" 分隔) - source: String, // 平台标识 - pic: String, // 大封面 URL - mPic: String, // 中封面 URL - sPic: String, // 小封面 URL - albumName: String, // 合集名 - albumId: String, // 合集 ID - interval: String, // 时长 "mm:ss" - qualities: { // 音质 -> 文件大小 + id: String, // 鍞竴鏍囪瘑 + name: String, // 鍚嶇О + artists: String, // 鍒涗綔鑰咃紙鐢?"銆? 鍒嗛殧锛? source: String, // 骞冲彴鏍囪瘑 + pic: String, // 澶у皝闈?URL + mPic: String, // 涓皝闈?URL + sPic: String, // 灏忓皝闈?URL + albumName: String, // 鍚堥泦鍚? albumId: String, // 鍚堥泦 ID + interval: String, // 鏃堕暱 "mm:ss" + qualities: { // 闊宠川 -> 鏂囦欢澶у皬 '128k': '4.2M', '320k': '8.5M', 'flac': '35M' } } ], - total: Number, // 总数量 - page: Number, // 当前页码 - limit: Number, // 每页数量 - allPage: Number, // 总页数 - source: String // 平台标识 + total: Number, // 鎬绘暟閲? page: Number, // 褰撳墠椤电爜 + limit: Number, // 姣忛〉鏁伴噺 + allPage: Number, // 鎬婚〉鏁? source: String // 骞冲彴鏍囪瘑 } ``` -### 歌词统一格式 +### 姝岃瘝缁熶竴鏍煎紡 ```javascript { - lyric: String, // 普通歌词 - tlyric: String, // 翻译歌词 - qrc: String, // 逐字歌词 - roma: String // 音译歌词 + lyric: String, // 鏅€氭瓕璇? tlyric: String, // 缈昏瘧姝岃瘝 + qrc: String, // 閫愬瓧姝岃瘝 + roma: String // 闊宠瘧姝岃瘝 } -// 注意:至少返回 lyric 或 qrc 之一 +// 娉ㄦ剰锛氳嚦灏戣繑鍥?lyric 鎴?qrc 涔嬩竴 ``` -### 列表详情统一格式 +### 鍒楄〃璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 内容列表,格式同搜索结果 */], + list: [/* 鍐呭鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 列表名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 创建者 - } + name: String, // 鍒楄〃鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 鍒涘缓鑰? } } ``` -### 合集详情统一格式 +### 鍚堥泦璇︽儏缁熶竴鏍煎紡 ```javascript { - list: [/* 内容列表,格式同搜索结果 */], + list: [/* 鍐呭鍒楄〃锛屾牸寮忓悓鎼滅储缁撴灉 */], page: Number, limit: Number, total: Number, source: String, info: { - name: String, // 合集名 - img: String, // 封面图 - desc: String, // 描述 - author: String // 创作者 - } + name: String, // 鍚堥泦鍚? img: String, // 灏侀潰鍥? desc: String, // 鎻忚堪 + author: String // 鍒涗綔鑰? } } ``` --- -## 示例代码详解 +## 绀轰緥浠g爜璇﹁В -### 完整插件模板 +### 瀹屾暣鎻掍欢妯℃澘 ```javascript /** - * @name 平台A音源 - * @description 音源插件示例 + * @name 骞冲彴A闊虫簮 + * @description 闊虫簮鎻掍欢绀轰緥 * @version 3.0.0 - * @author 开发者 - * - * 支持平台: 平台A - * 支持音质: 128k, 320k, flac + * @author 寮€鍙戣€? * + * 鏀寔骞冲彴: 骞冲彴A + * 鏀寔闊宠川: 128k, 320k, flac */ 'use strict' -// ==================== 核心导入 ==================== +// ==================== 鏍稿績瀵煎叆 ==================== const axios = require('axios') const crypto = require('crypto') -// ==================== 配置区域 ==================== +// ==================== 閰嶇疆鍖哄煙 ==================== -// 从 global.env 读取环境变量(JSON 格式) -const env = global.env || {} +// 浠?global.env 璇诲彇鐜鍙橀噺锛圝SON 鏍煎紡锛?const env = global.env || {} -// 平台标识(固定值,单平台插件) +// 骞冲彴鏍囪瘑锛堝浐瀹氬€硷紝鍗曞钩鍙版彃浠讹級 const PLATFORM = 'A' -// 服务端配置 -const CONFIG = { +// 鏈嶅姟绔厤缃?const CONFIG = { serverUrl: env.SERVER_URL || 'https://api.example.com', apiKey: env.API_KEY || '', timeout: 10000 } -// 支持的音质 -const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] +// 鏀寔鐨勯煶璐?const SUPPORT_QUALITIES = ['128k', '320k', 'flac'] -// ==================== 工具函数 ==================== +// ==================== 宸ュ叿鍑芥暟 ==================== /** - * 文件大小格式化 - * @param {number} size - 字节数 - * @returns {string} 格式化后的字符串 + * 鏂囦欢澶у皬鏍煎紡鍖? * @param {number} size - 瀛楄妭鏁? * @returns {string} 鏍煎紡鍖栧悗鐨勫瓧绗︿覆 */ function sizeFormate(size) { if (!size || isNaN(size)) return '' @@ -483,9 +435,8 @@ function sizeFormate(size) { } /** - * 播放时间格式化 - * @param {number} time - 秒数 - * @returns {string} 格式化后的字符串 + * 鎾斁鏃堕棿鏍煎紡鍖? * @param {number} time - 绉掓暟 + * @returns {string} 鏍煎紡鍖栧悗鐨勫瓧绗︿覆 */ function formatPlayTime(time) { if (!time || isNaN(time)) return '--/--' @@ -495,20 +446,18 @@ function formatPlayTime(time) { } /** - * 创作者名称格式化 - * @param {Array} artistList - 创作者列表 - * @returns {string} 用 "、" 连接的创作者名 + * 鍒涗綔鑰呭悕绉版牸寮忓寲 + * @param {Array} artistList - 鍒涗綔鑰呭垪琛? * @returns {string} 鐢?"銆? 杩炴帴鐨勫垱浣滆€呭悕 */ function formatArtistName(artistList) { if (!artistList || !Array.isArray(artistList)) return '' - return artistList.map(a => a.name || a).join('、') + return artistList.map(a => a.name || a).join('銆?) } /** - * HTML 实体解码 - * @param {string} str - 含 HTML 实体的字符串 - * @returns {string} 解码后的字符串 - */ + * HTML 瀹炰綋瑙g爜 + * @param {string} str - 鍚?HTML 瀹炰綋鐨勫瓧绗︿覆 + * @returns {string} 瑙g爜鍚庣殑瀛楃涓? */ function decodeName(str) { if (!str) return '' return str @@ -520,35 +469,31 @@ function decodeName(str) { .replace(/ /g, ' ') } -// ==================== 核心功能 ==================== +// ==================== 鏍稿績鍔熻兘 ==================== /** - * 搜索内容 - * @param {string} str - 搜索关键词 - * @param {number} page - 页码,从 1 开始 - * @param {number} limit - 每页数量 - * @returns {Promise} 搜索结果 + * 鎼滅储鍐呭 + * @param {string} str - 鎼滅储鍏抽敭璇? * @param {number} page - 椤电爜锛屼粠 1 寮€濮? * @param {number} limit - 姣忛〉鏁伴噺 + * @returns {Promise} 鎼滅储缁撴灉 */ async function musicSearch(str, page = 1, limit = 20) { - // 构造请求参数 - const params = { + // 鏋勯€犺姹傚弬鏁? const params = { keyword: str, page: page, limit: limit } - // 发送请求 - const url = `${CONFIG.serverUrl}/search` + // 鍙戦€佽姹? const url = `${CONFIG.serverUrl}/search` const response = await axios.get(url, { params, headers: { 'X-API-Key': CONFIG.apiKey }, timeout: CONFIG.timeout }) - // 解析数据 + // 瑙f瀽鏁版嵁 const data = response.data - // 转换为统一格式 + // 杞崲涓虹粺涓€鏍煎紡 const list = data.items.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -578,19 +523,17 @@ async function musicSearch(str, page = 1, limit = 20) { } /** - * 获取播放 URL - * @param {string} id - 内容 ID - * @param {string} quality - 音质标识 - * @returns {Promise} 播放 URL + * 鑾峰彇鎾斁 URL + * @param {string} id - 鍐呭 ID + * @param {string} quality - 闊宠川鏍囪瘑 + * @returns {Promise} 鎾斁 URL */ async function getUrl(id, quality) { - // 检查音质 - if (!SUPPORT_QUALITIES.includes(quality)) { + // 妫€鏌ラ煶璐? if (!SUPPORT_QUALITIES.includes(quality)) { quality = SUPPORT_QUALITIES[0] } - // 发送请求 - const url = `${CONFIG.serverUrl}/url` + // 鍙戦€佽姹? const url = `${CONFIG.serverUrl}/url` const response = await axios.get(url, { params: { id: id, quality: quality }, headers: { 'X-API-Key': CONFIG.apiKey }, @@ -600,16 +543,16 @@ async function getUrl(id, quality) { const data = response.data if (!data.url || !data.url.startsWith('http')) { - throw new Error('获取链接失败') + throw new Error('鑾峰彇閾炬帴澶辫触') } return data.url } /** - * 获取歌词 - * @param {string} id - 内容 ID - * @returns {Promise} 歌词对象 + * 鑾峰彇姝岃瘝 + * @param {string} id - 鍐呭 ID + * @returns {Promise} 姝岃瘝瀵硅薄 */ async function getLyric(id) { const url = `${CONFIG.serverUrl}/lyric` @@ -628,11 +571,11 @@ async function getLyric(id) { } /** - * 获取列表详情 - * @param {string} id - 列表 ID - * @param {number} page - 页码 - * @param {number} limit - 每页数量 - * @returns {Promise} 列表详情 + * 鑾峰彇鍒楄〃璇︽儏 + * @param {string} id - 鍒楄〃 ID + * @param {number} page - 椤电爜 + * @param {number} limit - 姣忛〉鏁伴噺 + * @returns {Promise} 鍒楄〃璇︽儏 */ async function songList(id, page = 1, limit = 20) { const url = `${CONFIG.serverUrl}/list` @@ -644,7 +587,7 @@ async function songList(id, page = 1, limit = 20) { const data = response.data - // 转换内容列表 + // 杞崲鍐呭鍒楄〃 const list = data.items.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -675,10 +618,10 @@ async function songList(id, page = 1, limit = 20) { } /** - * 获取合集详情 - * @param {string} id - 合集 ID - * @param {number} page - 页码 - * @returns {Promise} 合集详情 + * 鑾峰彇鍚堥泦璇︽儏 + * @param {string} id - 鍚堥泦 ID + * @param {number} page - 椤电爜 + * @returns {Promise} 鍚堥泦璇︽儏 */ async function album(id, page = 1) { const url = `${CONFIG.serverUrl}/album` @@ -690,7 +633,7 @@ async function album(id, page = 1) { const data = response.data - // 转换内容列表 + // 杞崲鍐呭鍒楄〃 const list = data.items.map(item => ({ id: String(item.id), name: decodeName(item.name), @@ -721,8 +664,7 @@ async function album(id, page = 1) { } /** - * 获取热搜词 - * @returns {Promise} 热搜列表 + * 鑾峰彇鐑悳璇? * @returns {Promise} 鐑悳鍒楄〃 */ async function hotSearch() { const url = `${CONFIG.serverUrl}/hotsearch` @@ -737,10 +679,10 @@ async function hotSearch() { } } -// ==================== 插件导出 ==================== +// ==================== 鎻掍欢瀵煎嚭 ==================== module.exports = { - // 核心功能 + // 鏍稿績鍔熻兘 musicSearch, getUrl, getLyric, @@ -748,23 +690,23 @@ module.exports = { album, hotSearch, - // 插件信息 + // 鎻掍欢淇℃伅 pluginInfo: { info: { id: PLATFORM, - name: '平台A', - description: '平台A插件', + name: '骞冲彴A', + description: '骞冲彴A鎻掍欢', version: '3' }, env: [ - { key: 'SERVER_URL', name: '服务端地址', description: '自定义服务端地址' }, - { key: 'API_KEY', name: 'API密钥', description: '服务端API密钥' } + { key: 'SERVER_URL', name: '鏈嶅姟绔湴鍧€', description: '鑷畾涔夋湇鍔$鍦板潃' }, + { key: 'API_KEY', name: 'API瀵嗛挜', description: '鏈嶅姟绔疉PI瀵嗛挜' } ], ext: [], quality: [ - { name: '标准音质', ui: '标', id: '128k' }, - { name: '高品音质', ui: 'HQ', id: '320k' }, - { name: '无损音质', ui: 'SQ', id: 'flac' } + { name: '鏍囧噯闊宠川', ui: '鏍?, id: '128k' }, + { name: '楂樺搧闊宠川', ui: 'HQ', id: '320k' }, + { name: '鏃犳崯闊宠川', ui: 'SQ', id: 'flac' } ], supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] } @@ -773,24 +715,21 @@ module.exports = { --- -## 平台配置参考 +## 骞冲彴閰嶇疆鍙傝€? +### 鍚勫钩鍙版爣璇嗗鐓ц〃 -### 各平台标识对照表 - -| 平台 | 标识 | 常见 ID 字段 | +| 骞冲彴 | 鏍囪瘑 | 甯歌 ID 瀛楁 | |------|------|-------------| -| 平台A | `A` | `id` | -| 平台B | `B` | `id` | -| 平台C | `C` | `id`, `hash` | -| 平台D | `D` | `hash`, `id` | -| 平台E | `E` | `id` | -| 平台F | `F` | `id` | - -### 音质支持参考 +| 骞冲彴A | `A` | `id` | +| 骞冲彴B | `B` | `id` | +| 骞冲彴C | `C` | `id`, `hash` | +| 骞冲彴D | `D` | `hash`, `id` | +| 骞冲彴E | `E` | `id` | +| 骞冲彴F | `F` | `id` | +### 闊宠川鏀寔鍙傝€? ```javascript -// 各平台常见音质配置 -const PLATFORM_QUALITIES = { +// 鍚勫钩鍙板父瑙侀煶璐ㄩ厤缃?const PLATFORM_QUALITIES = { A: ['128k', '320k', 'flac', 'flac24bit', 'hires'], B: ['128k', '320k', 'flac', 'flac24bit', 'hires'], C: ['128k', '320k', 'flac', 'flac24bit'], @@ -802,70 +741,60 @@ const PLATFORM_QUALITIES = { --- -## 常见问题 +## 甯歌闂 -### Q1: 如何从其他系统迁移到本系统? +### Q1: 濡備綍浠庡叾浠栫郴缁熻縼绉诲埌鏈郴缁燂紵 -**A**: 参考本文档的"从其他平台迁移指南"章节,主要修改点: -1. 将 `globalThis.lx.request` 改为 `axios` -2. 将事件监听改为函数导出 -3. 修改数据返回格式 -4. 添加 `module.exports` 导出 -5. 环境变量从 `global.env` 读取 +**A**: 鍙傝€冩湰鏂囨。鐨?浠庡叾浠栧钩鍙拌縼绉绘寚鍗?绔犺妭锛屼富瑕佷慨鏀圭偣锛?1. 灏?`globalThis.lx.request` 鏀逛负 `axios` +2. 灏嗕簨浠剁洃鍚敼涓哄嚱鏁板鍑?3. 淇敼鏁版嵁杩斿洖鏍煎紡 +4. 娣诲姞 `module.exports` 瀵煎嚭 +5. 鐜鍙橀噺浠?`global.env` 璇诲彇 -### Q2: 插件加载失败怎么办? +### Q2: 鎻掍欢鍔犺浇澶辫触鎬庝箞鍔烇紵 -**A**: 检查以下几点: -1. 文件语法是否正确:`node -c your-plugin.js` -2. `pluginInfo` 是否完整 -3. 导出的函数名是否正确 -4. 依赖模块是否已安装(如 `axios`) +**A**: 妫€鏌ヤ互涓嬪嚑鐐癸細 +1. 鏂囦欢璇硶鏄惁姝g‘锛歚node -c your-plugin.js` +2. `pluginInfo` 鏄惁瀹屾暣 +3. 瀵煎嚭鐨勫嚱鏁板悕鏄惁姝g‘ +4. 渚濊禆妯″潡鏄惁宸插畨瑁咃紙濡?`axios`锛? +### Q3: 鎼滅储杩斿洖绌虹粨鏋滐紵 -### Q3: 搜索返回空结果? - -**A**: 检查: -1. API 请求是否成功(查看日志) -2. 响应数据解析是否正确 -3. 数据格式是否符合规范 -4. 返回的 `source` 是否与平台标识一致 - -### Q4: 播放失败? - -**A**: 检查: -1. `getUrl` 返回的 URL 是否有效 -2. URL 是否以 `http` 开头 -3. 音质标识是否正确 -4. 是否有跨域或权限问题 - -### Q5: 用户如何配置插件? - -**A**: 用户通过设置界面配置环境变量: +**A**: 妫€鏌ワ細 +1. API 璇锋眰鏄惁鎴愬姛锛堟煡鐪嬫棩蹇楋級 +2. 鍝嶅簲鏁版嵁瑙f瀽鏄惁姝g‘ +3. 鏁版嵁鏍煎紡鏄惁绗﹀悎瑙勮寖 +4. 杩斿洖鐨?`source` 鏄惁涓庡钩鍙版爣璇嗕竴鑷? +### Q4: 鎾斁澶辫触锛? +**A**: 妫€鏌ワ細 +1. `getUrl` 杩斿洖鐨?URL 鏄惁鏈夋晥 +2. URL 鏄惁浠?`http` 寮€澶?3. 闊宠川鏍囪瘑鏄惁姝g‘ +4. 鏄惁鏈夎法鍩熸垨鏉冮檺闂 +### Q5: 鐢ㄦ埛濡備綍閰嶇疆鎻掍欢锛? +**A**: 鐢ㄦ埛閫氳繃璁剧疆鐣岄潰閰嶇疆鐜鍙橀噺锛? ``` -SERVER_URL = 自定义服务端地址 -API_KEY = 用户的API密钥 +SERVER_URL = 鑷畾涔夋湇鍔$鍦板潃 +API_KEY = 鐢ㄦ埛鐨凙PI瀵嗛挜 ``` -插件通过 `global.env` 读取: - +鎻掍欢閫氳繃 `global.env` 璇诲彇锛? ```javascript const env = global.env || {} -const SERVER_URL = env.SERVER_URL || '默认地址' +const SERVER_URL = env.SERVER_URL || '榛樿鍦板潃' ``` --- -## 附录 +## 闄勫綍 -### 参考资源 +### 鍙傝€冭祫婧? +- Node.js 瀹樻柟鏂囨。 +- axios 鏂囨。 -- Node.js 官方文档 -- axios 文档 +### 鐗堟湰鍘嗗彶 -### 版本历史 - -- v1.0.0 (2024-05-28): 初始版本 +- v1.0.0 (2024-05-28): 鍒濆鐗堟湰 --- -**文档结束** +**鏂囨。缁撴潫** diff --git a/README.md b/README.md index aabce30..7547477 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,38 @@ -# Koneko API for QZ-Music +# Koneko API for QZ-Music -QZ Music v2/v3 音源插件集合。每个插件仅包含一个平台,官方搜索 + 多 API 音源自动测速容灾。 +QZ Music v2/v3 闊虫簮鎻掍欢闆嗗悎銆傛瘡涓彃浠朵粎鍖呭惈涓€涓钩鍙帮紝瀹樻柟鎼滅储 + 澶?API 闊虫簮鑷姩娴嬮€熷鐏俱€? +## 鎻掍欢鍒楄〃 -## 插件列表 - -| 文件 | 平台 | 版本 | 说明 | +| 鏂囦欢 | 骞冲彴 | 鐗堟湰 | 璇存槑 | |------|------|------|------| -| `Koneko_QQ音乐_v0.0.4.js` | QQ音乐 | 0.0.4 | 官方搜索 + 10路API音源容灾 | -| `Koneko_酷狗音乐_v0.0.4.js` | 酷狗音乐 | 0.0.4 | 官方搜索 + 8路API音源容灾 | -| `Koneko_酷我音乐_v0.0.4.js` | 酷我音乐 | 0.0.4 | 官方搜索 + 9路API音源容灾 | -| `Koneko_网易云音乐_v0.0.4.js` | 网易云音乐 | 0.0.4 | 官方搜索 + 6路API音源容灾 + Cookie全功能 | -| `Koneko_咪咕音乐_v0.0.4.js` | 咪咕音乐 | 0.0.4 | 官方搜索 + 8路API音源容灾 | -| `Koneko_GIT音源_v0.0.4.js` | GIT音源 | 0.0.4 | 纯音源 + 2路API音源容灾 | +| `Koneko_QQ闊充箰_v0.0.1.js` | QQ闊充箰 | 0.0.1 | 瀹樻柟鎼滅储 + 10璺疉PI闊虫簮瀹圭伨 | +| `Koneko_閰风嫍闊充箰_v0.0.1.js` | 閰风嫍闊充箰 | 0.0.1 | 瀹樻柟鎼滅储 + 8璺疉PI闊虫簮瀹圭伨 | +| `Koneko_閰锋垜闊充箰_v0.0.1.js` | 閰锋垜闊充箰 | 0.0.1 | 瀹樻柟鎼滅储 + 9璺疉PI闊虫簮瀹圭伨 | +| `Koneko_缃戞槗浜戦煶涔恄v0.0.1.js` | 缃戞槗浜戦煶涔?| 0.0.1 | 瀹樻柟鎼滅储 + 6璺疉PI闊虫簮瀹圭伨 + Cookie鍏ㄥ姛鑳?| +| `Koneko_鍜挄闊充箰_v0.0.1.js` | 鍜挄闊充箰 | 0.0.1 | 瀹樻柟鎼滅储 + 8璺疉PI闊虫簮瀹圭伨 | +| `Koneko_GIT闊虫簮_v0.0.1.js` | GIT闊虫簮 | 0.0.1 | 绾煶婧?+ 2璺疉PI闊虫簮瀹圭伨 | -## 环境变量 +## 鐜鍙橀噺 -在 QZ Music 设置中配置环境变量,插件通过 `global.env` 读取: - -| Key | 适用平台 | 说明 | +鍦?QZ Music 璁剧疆涓厤缃幆澧冨彉閲忥紝鎻掍欢閫氳繃 `global.env` 璇诲彇锛? +| Key | 閫傜敤骞冲彴 | 璇存槑 | |-----|---------|------| -| `ceru_key` | 全部 | 聆澜音源API密钥(可选) | -| `cookie` | 网易云 | 网易云Cookie,用于搜索/每日推荐/私人FM/我喜欢的音乐等 | -| `playlist_url` | 网易云 | 网易云个人主页链接,用于获取个人歌单 | +| `ceru_key` | 鍏ㄩ儴 | 鑱嗘緶闊虫簮API瀵嗛挜锛堝彲閫夛級 | +| `cookie` | 缃戞槗浜?| 缃戞槗浜慍ookie锛岀敤浜庢悳绱?姣忔棩鎺ㄨ崘/绉佷汉FM/鎴戝枩娆㈢殑闊充箰绛?| +| `playlist_url` | 缃戞槗浜?| 缃戞槗浜戜釜浜轰富椤甸摼鎺ワ紝鐢ㄤ簬鑾峰彇涓汉姝屽崟 | -## 音质标识 +## 闊宠川鏍囪瘑 -| ID | 显示 | +| ID | 鏄剧ず | |----|------| -| `standard` | 标准音质 (128k) | -| `exhigh` | 高品音质 (320k) | -| `lossless` | 无损音质 (FLAC) | +| `standard` | 鏍囧噯闊宠川 (128k) | +| `exhigh` | 楂樺搧闊宠川 (320k) | +| `lossless` | 鏃犳崯闊宠川 (FLAC) | | `hires` | Hi-Res | -## 容灾机制 +## 瀹圭伨鏈哄埗 -`getUrl` 采用并发请求多个 API,取第一个成功返回的 URL。聆澜 API 需要配置 `ceru_key`,未配置时自动跳过。 - -## 版权声明 - -本插件仅用于学习交流,不存储、不提供任何音乐文件。音乐版权归各平台所有。 +`getUrl` 閲囩敤骞跺彂璇锋眰澶氫釜 API锛屽彇绗竴涓垚鍔熻繑鍥炵殑 URL銆傝亞婢?API 闇€瑕侀厤缃?`ceru_key`锛屾湭閰嶇疆鏃惰嚜鍔ㄨ烦杩囥€? +## 鐗堟潈澹版槑 +鏈彃浠朵粎鐢ㄤ簬瀛︿範浜ゆ祦锛屼笉瀛樺偍銆佷笉鎻愪緵浠讳綍闊充箰鏂囦欢銆傞煶涔愮増鏉冨綊鍚勫钩鍙版墍鏈夈€?