# Koneko QZ Music v2 鎻掍欢寮€鍙戦伩鍧戞寚鍗? > 鐗堟湰: 0.0.3 | 浣滆€? 浜戞眬(Miao-moe) | 鐩爣: 鏀寔鍒板埆鐨?AI 缁х画寮€鍙? --- ## 涓€銆侀」鐩儗鏅? QZ Music v2 鏄竴娆?Android 闊充箰鎾斁鍣紝鏀寔閫氳繃**鎷撳睍鎻掍欢**鎺ュ叆澶氬钩鍙伴煶婧愩€傛彃浠剁郴缁熷熀浜?Node.js 杩愯鏃讹紙Javet/V8锛夛紝姣忎釜鎻掍欢鏄竴涓崟鐙殑 `.js` 鏂囦欢锛岄€氳繃 `module.exports` 瀵煎嚭鎺ュ彛銆? ### 1.1 鎻掍欢鍔犺浇鏈哄埗 - canary 12-4 鐗堟湰涔嬪墠锛氱洿鎺ュ姞杞?`.js` 鏂囦欢 - **canary 12-4 鍙婁箣鍚?*锛氶渶瑕?`鏂囦欢澶?+ plugin.json + index.js` 缁撴瀯锛堜絾鐢ㄦ埛瑕佹眰鍙敤 `.js`锛屾墍浠ュ綋鍓嶇増鏈槸鍗曟枃浠讹級 - 杩愯鏃剁幆澧冨彉閲忛€氳繃 `global.env` 璁块棶锛?*涓嶆槸** `process.env` ### 1.2 鎻掍欢瀵煎嚭鏍煎紡 ```js module.exports = { musicSearch: { search: fn, tipSearch: fn, hotSearch: fn }, tipSearch: { getList: fn }, hotSearch: { getList: fn }, getUrl: fn, pluginInfo: { info: {...}, env: [...], ext: [...], quality: [...], supportFunc: [] }, // 缃戞槗浜戠壒鏈? userPlaylist: fn, dailyRecommend: fn, personalFm: fn, myLikedSongs: fn } ``` --- ## 浜屻€丣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 | ### 2.1 Promise.allSettled 鏇夸唬鏂规 ```js // 鉂?涓嶆敮鎸?Promise.allSettled(promises) // 鉁?姝g‘鍐欐硶 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`锛? ### 3.1 姝g‘鐨勬悳绱㈣繑鍥炴牸寮? ```js return { list: [ { 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 qualities: { standard: '3.21MB', exhigh: '7.85MB', lossless: '25.3MB', hires: '48.2MB' } } ], allPage: 5, // 鎬婚〉鏁? limit: 30, // 姣忛〉鏉℃暟 total: 150, // 鎬绘潯鏁? source: 'tx' // 骞冲彴鏍囪瘑 } ``` ### 3.2 瀛楁鍚嶅鐓ц〃 | 鍚箟 | 姝g‘瀛楁鍚?| 閿欒瀛楁鍚?| |------|----------|----------| | 姝屾墜 | `artists` | `artist` | | 灏侀潰鍥?| `pic` / `mPic` / `sPic` | `picUrl` | | 鏃堕暱 | `interval` (瀛楃涓?m:ss) | `duration` | | 姝屾洸鍒楄〃 | `list` | `songs` | --- ## 鍥涖€佸悇骞冲彴 API 韪╁潙璁板綍 ### 4.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 闊宠川鍙傛暟**锛欰PI 闇€瑕佸甫 `k`锛屽 `320k`锛屼笉鏄?`320` ### 4.2 閰风嫍闊充箰 (kg) **鎼滅储鎺ュ彛**锛歚http://mobilecdn.kugou.com/api/v3/search/song` **閲嶈**锛氳繑鍥炲瓧娈垫槸 `errcode`锛堜笉鏄?`error_code`锛夛紒 ```js // 鉁?姝g‘ if (result.errcode !== 0) { ... } // 鉂?閿欒 if (result.error_code !== 0) { ... } ``` **灏侀潰鍥?*锛氭悳绱㈢粨鏋滆嚜甯?`imgurl` 瀛楁锛屾浛鎹?`{size}` 涓?`400` ```js var picUrl = item.imgurl ? item.imgurl.replace('{size}', '400') : '' ``` **getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` ### 4.3 閰锋垜闊充箰 (kw) **鎼滅储鎺ュ彛**锛歚http://search.kuwo.cn/r.s` **灏侀潰鍥?*锛歚https://img2.kuwo.cn/star/albumcover/300/{ALBUMID}.jpg` **闊宠川淇℃伅**锛氬湪 `N_MINFO` 瀛楁涓紝鏍煎紡涓?`level:xxx,bitrate:xxx,format:xxx,size:xxx;...` ```js var parts = info.N_MINFO.split(';') for (var j = 0; j < parts.length; j++) { var m = parts[j].match(/level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/) if (m) { if (m[2] === '20900') qualities.jymaster = m[4] else if (m[2] === '4000') qualities.hires = m[4] else if (m[2] === '2000') qualities.lossless = m[4] else if (m[2] === '320') qualities.exhigh = m[4] else if (m[2] === '128') qualities.standard = m[4] } } ``` **getUrl 闊宠川鍙傛暟**锛歚128k` / `320k` / `999k` ### 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鐨勶級 **鍔犲瘑鎺ュ彛**锛?- `eapi`锛欰ES-128-ECB锛宬ey = `e82ckenh8dichen8` - `weapi`锛欰ES-128-CBC + RSA锛堢敤浜庨渶瑕佺櫥褰曠殑鎺ュ彛锛? **ext 鍔熻兘**锛?- `userPlaylist`锛氶渶瑕?`playlist_url` 鐜鍙橀噺锛堢綉鏄撲簯涓汉涓婚〉閾炬帴锛?- `dailyRecommend`锛氭瘡鏃ユ帹鑽?- `personalFm`锛氱浜篎M - `myLikedSongs`锛氭垜鍠滄鐨勯煶涔? ### 4.5 鍜挄闊充箰 (mg) **鎼滅储绛惧悕**锛歁D5 鎷兼帴 ```js var sign = crypto.createHash('md5').update( str + signatureMd5 + 'yyapp2d16148780a1dcc7408e06336b98cfd50' + deviceId + time ).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` ### 4.6 GIT闊虫簮 (git) - 鏃犳悳绱㈠姛鑳斤紙杩斿洖绌哄垪琛級 - 绾煶婧愭彃浠讹紝鍙湁 `getUrl` - getUrl 闊宠川鍙傛暟锛歚128k` / `320k` / `999k` --- ## 浜斻€乬etUrl 娴嬮€熷鐏鹃€昏緫 鎵€鏈夊钩鍙扮粺涓€浣跨敤**骞跺彂娴嬮€?*妯″紡锛氬悓鏃惰姹傚涓?API锛屽彇绗竴涓垚鍔熺殑缁撴灉銆? ```js function getUrl(songId, quality) { var apis = buildApis(songId, quality) var promises = [] for (var i = 0; i < apis.length; i++) { (function(api) { promises.push( httpGet(api.url, api.headers, 8000).then(function(res) { var url = api.extract(res) if (url) return { name: api.name, url: url } throw new Error('no url') }).catch(function(err) { throw err }) ) })(apis[i]) } return 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 '' }) } ``` ### 5.1 闊虫簮 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 | --- ## 鍏€佺増鏈彿绠$悊瑙勮寖 **鎵€鏈夊钩鍙扮粺涓€鐗堟湰鍙?*锛屾瘡娆′慨鏀瑰叏閮ㄥ崌绾э細 - 褰撳墠鐗堟湰锛歚0.0.3` - 涓嬫淇敼锛歚0.0.1` - 鍐嶄笅娆★細`0.0.5` 鏂囦欢鍚嶆牸寮忥細`Koneko_{骞冲彴鍚峿_v{鐗堟湰鍙穧.js` --- ## 涓冦€佸父瑙佹姤閿欎笌瑙e喅 | 鎶ラ敊 | 鍘熷洜 | 瑙e喅 | |------|------|------| | `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 || '' // 缃戞槗浜慍ookie var PLAYLIST_URL = env.playlist_url || '' // 缃戞槗浜戜釜浜轰富椤甸摼鎺?``` 鍦?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` --- ## 鍗併€佸悗缁紑鍙戝缓璁? 1. **姣忔淇敼鍏ㄩ儴骞冲彴缁熶竴鍗囩骇鐗堟湰鍙?* 2. **鍏堝湪娴忚鍣? curl 娴嬭瘯 API 鏄惁鍙敤** 3. **娉ㄦ剰 Javet 鍏煎鎬э紝閬垮厤鐜颁唬 JS 璇硶** 4. **鎼滅储杩斿洖鍔″繀鍖呭惈 `list` 瀛楁** 5. **getUrl 娉ㄦ剰鍚勫钩鍙伴煶璐ㄥ弬鏁版牸寮忓樊寮?* 6. **灏侀潰鍥?URL 纭繚鍙闂紙娉ㄦ剰璺ㄥ煙鍜岄槻鐩楅摼锛?* 7. **鎵€鏈夊紓姝ユ搷浣滃姞 `.catch()` 鍏滃簳** 8. **浼樺厛浣跨敤瀹樻柟鎼滅储鎺ュ彛锛岄煶婧愮敤绗笁鏂?API 瀹圭伨**