From 11c1ed5928e9e9d7ac09e17afd8d536ab2dbda0b Mon Sep 17 00:00:00 2001 From: overwriter <49340226+s12mmm3@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:25:55 +0800 Subject: [PATCH] refactor: Decrypt according to the 'e_r' parameter (#1454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: whether the eapi return value is needs to be encrypted is controlled by the param ‘e_r’ (refer to NeteaseCloudMusicApi) * feat(crypto/hook): support 'api' crypto --- src/crypto.js | 9 +++ src/hook.js | 172 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 121 insertions(+), 60 deletions(-) diff --git a/src/crypto.js b/src/crypto.js index aa9b952819..51f72d5468 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -41,6 +41,15 @@ module.exports = { }; }, }, + api: { + encryptRequest: (url, object) => { + url = parse(url); + return { + url: url.href.replace(/\w*api/, 'api'), + body: bodyify(object), + }; + }, + }, linuxapi: { encrypt: (buffer) => encrypt(buffer, linuxapiKey), decrypt: (buffer) => decrypt(buffer, linuxapiKey), diff --git a/src/hook.js b/src/hook.js index 1bccf427de..3ee3bca111 100644 --- a/src/hook.js +++ b/src/hook.js @@ -107,6 +107,8 @@ const domainList = [ 'iplay.163.com', 'look.163.com', 'y.163.com', + 'interface.music.163.com', + 'interface3.music.163.com', ]; hook.request.before = (ctx) => { @@ -117,7 +119,7 @@ hook.request.before = (ctx) => { : (req.socket.encrypted ? 'https:' : 'http:') + '//' + (domainList.some((domain) => - (req.headers.host || '').endsWith(domain) + (req.headers.host || '').includes(domain) ) ? req.headers.host : null)) + req.url; @@ -146,7 +148,7 @@ hook.request.before = (ctx) => { hook.target.host.has(host) ) && req.method === 'POST' && - (url.path === '/api/linux/forward' || url.path.startsWith('/eapi/')) + (url.path.startsWith('/eapi/') || url.path.startsWith('/api/')) ) { return request .read(req) @@ -162,41 +164,74 @@ hook.request.before = (ctx) => { return; // look living/cloudupload eapi can not be decrypted req.headers['Accept-Encoding'] = 'gzip, deflate'; // https://blog.csdn.net/u013022222/article/details/51707352 if (body) { - let data; const netease = {}; netease.pad = (body.match(/%0+$/) || [''])[0]; - netease.forward = url.path === '/api/linux/forward'; - if (netease.forward) { - data = JSON.parse( - crypto.linuxapi + if (url.path === '/api/linux/forward') { + netease.crypto = 'linuxapi' + } + else if (url.path.startsWith('/eapi/')) { + netease.crypto = 'eapi' + } + else if (url.path.startsWith('/api/')) { + netease.crypto = 'api' + } + let data; + switch (netease.crypto) { + case 'linuxapi': + data = JSON.parse( + crypto.linuxapi + .decrypt( + Buffer.from( + body.slice( + 8, + body.length - netease.pad.length + ), + 'hex' + ) + ) + .toString() + ); + netease.path = parse(data.url).path; + netease.param = data.params; + break; + case 'eapi': + data = crypto.eapi .decrypt( Buffer.from( body.slice( - 8, + 7, body.length - netease.pad.length ), 'hex' ) ) .toString() - ); - netease.path = parse(data.url).path; - netease.param = data.params; - } else { - data = crypto.eapi - .decrypt( - Buffer.from( - body.slice( - 7, - body.length - netease.pad.length - ), - 'hex' - ) - ) - .toString() - .split('-36cd479b6b5-'); - netease.path = data[0]; - netease.param = JSON.parse(data[1]); + .split('-36cd479b6b5-'); + netease.path = data[0]; + netease.param = JSON.parse(data[1]); + if ( + netease.param.hasOwnProperty('e_r') && + (netease.param.e_r == 'true' || + netease.param.e_r == true) + ) { + // eapi's e_r is true, needs to be encrypted + netease.e_r = true; + } else { + netease.e_r = false; + } + break; + case 'api': + data = {}; + decodeURIComponent(body).split('&').forEach((pair) => { + let [key, value] = pair.split('='); + data[key] = value + }); + netease.path = url.path + netease.param = data + break; + default: + // unsupported crypto + break; } netease.path = netease.path.replace(/\/\d*$/, ''); ctx.netease = netease; @@ -292,15 +327,16 @@ hook.request.after = (ctx) => { /([^\\]"\s*:\s*)(\d{16,})(\s*[}|,])/g, '$1"$2L"$3' ); // for js precision - try { - netease.encrypted = false; - netease.jsonBody = JSON.parse(patch(buffer.toString())); - } catch (error) { - netease.encrypted = true; + + if (netease.e_r) { + // eapi's e_r is true, needs to be encrypted netease.jsonBody = JSON.parse( patch(crypto.eapi.decrypt(buffer).toString()) ); } + else { + netease.jsonBody = JSON.parse(patch(buffer.toString())); + } if (ENABLE_LOCAL_VIP) { const vipPath = '/api/music-vip-membership/client/vip/info'; @@ -466,7 +502,7 @@ hook.request.after = (ctx) => { /([^\\]"\s*:\s*)"(\d{16,})L"(\s*[}|,])/g, '$1$2$3' ); // for js precision - proxyRes.body = netease.encrypted + proxyRes.body = netease.e_r // eapi's e_r is true, needs to be encrypted ? crypto.eapi.encrypt(Buffer.from(body)) : body; }) @@ -526,14 +562,22 @@ const pretendPlay = (ctx) => { const { req, netease } = ctx; const turn = 'http://music.163.com/api/song/enhance/player/url'; let query; - if (netease.forward) { - const { id, br } = netease.param; - netease.param = { ids: `["${id}"]`, br }; - query = crypto.linuxapi.encryptRequest(turn, netease.param); - } else { - const { id, br, e_r, header } = netease.param; - netease.param = { ids: `["${id}"]`, br, e_r, header }; - query = crypto.eapi.encryptRequest(turn, netease.param); + const { id, br, e_r, header } = netease.param; + switch (netease.crypto) { + case 'linuxapi': + netease.param = { ids: `["${id}"]`, br }; + query = crypto.linuxapi.encryptRequest(turn, netease.param); + break; + case 'eapi': + case 'api': + netease.param = { ids: `["${id}"]`, br, e_r, header }; + if (netease.crypto == 'eapi') + query = crypto.eapi.encryptRequest(turn, netease.param); + else if (netease.crypto == 'api') + query = crypto.api.encryptRequest(turn, netease.param); + break; + default: + break; } req.url = query.url; req.body = query.body + netease.pad; @@ -543,26 +587,34 @@ const pretendPlayV1 = (ctx) => { const { req, netease } = ctx; const turn = 'http://music.163.com/api/song/enhance/player/url/v1'; let query; - if (netease.forward) { - const { id, level, immerseType } = netease.param; - netease.param = { - ids: `["${id}"]`, - level, - encodeType: 'flac', - immerseType, - }; - query = crypto.linuxapi.encryptRequest(turn, netease.param); - } else { - const { id, level, immerseType, e_r, header } = netease.param; - netease.param = { - ids: `["${id}"]`, - level, - encodeType: 'flac', - immerseType, - e_r, - header, - }; - query = crypto.eapi.encryptRequest(turn, netease.param); + const { id, level, immerseType, e_r, header } = netease.param; + switch (netease.crypto) { + case 'linuxapi': + netease.param = { + ids: `["${id}"]`, + level, + encodeType: 'flac', + immerseType, + }; + query = crypto.linuxapi.encryptRequest(turn, netease.param); + break; + case 'eapi': + case 'api': + netease.param = { + ids: `["${id}"]`, + level, + encodeType: 'flac', + immerseType, + e_r, + header, + }; + if (netease.crypto == 'eapi') + query = crypto.eapi.encryptRequest(turn, netease.param); + else if (netease.crypto == 'api') + query = crypto.api.encryptRequest(turn, netease.param); + break; + default: + break; } req.url = query.url; req.body = query.body + netease.pad;