feat: 支持抓包xeapi

This commit is contained in:
ElyPrism 2026-05-30 19:13:20 +08:00
parent 796befebd2
commit 1ac4faa8f2
No known key found for this signature in database
4 changed files with 147 additions and 57 deletions

View File

@ -1,6 +1,6 @@
{
"name": "api-clawer",
"version": "0.1.1",
"version": "0.2.0",
"description": "网易云音乐客户端抓包工具",
"main": "src/server/app.js",
"scripts": {
@ -16,12 +16,12 @@
"license": "MIT",
"packageManager": "pnpm@10.28.1",
"dependencies": {
"axios": "^1.14.0",
"axios": "^1.16.1",
"concurrently": "^8.2.2",
"dotenv": "^16.6.1",
"express": "^4.22.1",
"express": "^4.22.2",
"pino": "^6.14.0",
"pino-pretty": "^7.6.1",
"ws": "^8.20.0"
"ws": "^8.21.0"
}
}

138
pnpm-lock.yaml generated
View File

@ -9,8 +9,8 @@ importers:
.:
dependencies:
axios:
specifier: ^1.14.0
version: 1.14.0
specifier: ^1.16.1
version: 1.16.1
concurrently:
specifier: ^8.2.2
version: 8.2.2
@ -18,8 +18,8 @@ importers:
specifier: ^16.6.1
version: 16.6.1
express:
specifier: ^4.22.1
version: 4.22.1
specifier: ^4.22.2
version: 4.22.2
pino:
specifier: ^6.14.0
version: 6.14.0
@ -27,19 +27,23 @@ importers:
specifier: ^7.6.1
version: 7.6.1
ws:
specifier: ^8.20.0
version: 8.20.0
specifier: ^8.21.0
version: 8.21.0
packages:
'@babel/runtime@7.29.2':
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
'@babel/runtime@7.29.7':
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
engines: {node: '>=6.9.0'}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@ -66,11 +70,11 @@ packages:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
axios@1.14.0:
resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==}
axios@1.16.1:
resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==}
body-parser@1.20.4:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
body-parser@1.20.5:
resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
bytes@3.1.2:
@ -156,6 +160,15 @@ packages:
supports-color:
optional: true
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@ -200,8 +213,8 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
es-object-atoms@1.1.2:
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
@ -223,8 +236,8 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
express@4.22.2:
resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==}
engines: {node: '>= 0.10.0'}
fast-redact@3.5.0:
@ -241,8 +254,8 @@ packages:
flatstr@1.0.12:
resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
follow-redirects@1.16.0:
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
@ -297,14 +310,18 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@ -422,8 +439,8 @@ packages:
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
qs@6.14.2:
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
qs@6.15.2:
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
engines: {node: '>=0.6'}
quick-format-unescaped@4.0.4:
@ -471,12 +488,12 @@ packages:
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
shell-quote@1.8.3:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
shell-quote@1.8.4:
resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==}
engines: {node: '>= 0.4'}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
side-channel-list@1.0.1:
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
engines: {node: '>= 0.4'}
side-channel-map@1.0.1:
@ -575,8 +592,8 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@ -601,13 +618,19 @@ packages:
snapshots:
'@babel/runtime@7.29.2': {}
'@babel/runtime@7.29.7': {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
agent-base@6.0.2:
dependencies:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
ansi-regex@5.0.1: {}
ansi-styles@3.2.1:
@ -631,15 +654,17 @@ snapshots:
atomic-sleep@1.0.0: {}
axios@1.14.0:
axios@1.16.1:
dependencies:
follow-redirects: 1.15.11
follow-redirects: 1.16.0
form-data: 4.0.5
https-proxy-agent: 5.0.1
proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
- supports-color
body-parser@1.20.4:
body-parser@1.20.5:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
@ -649,7 +674,7 @@ snapshots:
http-errors: 2.0.1
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.14.2
qs: 6.15.2
raw-body: 2.5.3
type-is: 1.6.18
unpipe: 1.0.0
@ -711,7 +736,7 @@ snapshots:
date-fns: 2.30.0
lodash: 4.18.1
rxjs: 7.8.2
shell-quote: 1.8.3
shell-quote: 1.8.4
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
@ -729,7 +754,7 @@ snapshots:
date-fns@2.30.0:
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.29.7
dateformat@4.6.3: {}
@ -737,6 +762,10 @@ snapshots:
dependencies:
ms: 2.0.0
debug@4.4.3:
dependencies:
ms: 2.1.3
delayed-stream@1.0.0: {}
depd@2.0.0: {}
@ -772,7 +801,7 @@ snapshots:
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
es-object-atoms@1.1.2:
dependencies:
es-errors: 1.3.0
@ -781,7 +810,7 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
hasown: 2.0.4
escalade@3.2.0: {}
@ -791,11 +820,11 @@ snapshots:
etag@1.8.1: {}
express@4.22.1:
express@4.22.2:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.4
body-parser: 1.20.5
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.2
@ -814,7 +843,7 @@ snapshots:
parseurl: 1.3.3
path-to-regexp: 0.1.13
proxy-addr: 2.0.7
qs: 6.14.2
qs: 6.15.2
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.2
@ -845,14 +874,14 @@ snapshots:
flatstr@1.0.12: {}
follow-redirects@1.15.11: {}
follow-redirects@1.16.0: {}
form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
hasown: 2.0.4
mime-types: 2.1.35
forwarded@0.2.0: {}
@ -868,18 +897,18 @@ snapshots:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
es-object-atoms: 1.1.2
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
hasown: 2.0.4
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
es-object-atoms: 1.1.2
gopd@1.2.0: {}
@ -893,7 +922,7 @@ snapshots:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@ -905,6 +934,13 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@ -1008,7 +1044,7 @@ snapshots:
end-of-stream: 1.4.5
once: 1.4.0
qs@6.14.2:
qs@6.15.2:
dependencies:
side-channel: 1.1.0
@ -1072,9 +1108,9 @@ snapshots:
setprototypeof@1.2.0: {}
shell-quote@1.8.3: {}
shell-quote@1.8.4: {}
side-channel-list@1.0.0:
side-channel-list@1.0.1:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
@ -1098,7 +1134,7 @@ snapshots:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
side-channel-list: 1.0.0
side-channel-list: 1.0.1
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
@ -1174,7 +1210,7 @@ snapshots:
wrappy@1.0.2: {}
ws@8.20.0: {}
ws@8.21.0: {}
y18n@5.0.8: {}

View File

@ -6,6 +6,7 @@ const bodyify = require('querystring').stringify;
const eapiKey = 'e82ckenh8dichen8';
const linuxapiKey = 'rFgB&h#%2?^eDg:Q';
const xeapiKey = '723f08a8d77c4a3698a9722b71b3607b';
const decrypt = (buffer, key) => {
const decipher = crypto.createDecipheriv('aes-128-ecb', key, null);
@ -41,6 +42,29 @@ module.exports = {
};
},
},
xeapi: {
encrypt: (buffer) => encrypt(buffer, xeapiKey),
decrypt: (buffer) => decrypt(buffer, xeapiKey),
encryptRequest: (url, object) => {
url = parse(url);
const text = JSON.stringify(object);
const message = `nobody${url.path}use${text}md5forencrypt`;
const digest = crypto
.createHash('md5')
.update(message)
.digest('hex');
const data = `${url.path}-36cd479b6b5-${text}-36cd479b6b5-${digest}`;
return {
url: url.href.replace(/\w*api/, 'xeapi'),
body: bodyify({
params: module.exports.xeapi
.encrypt(Buffer.from(data))
.toString('hex')
.toUpperCase(),
}),
};
},
},
api: {
encryptRequest: (url, object) => {
url = parse(url);

View File

@ -133,6 +133,7 @@ hook.request.before = (ctx) => {
) &&
req.method === 'POST' &&
(url.path.startsWith('/eapi/') || // eapi
url.path.startsWith('/xeapi/') || // xeapi
url.path.startsWith('/api/linux/forward')) // linuxapi
) {
return request
@ -157,6 +158,8 @@ hook.request.before = (ctx) => {
netease.crypto = 'linuxapi';
} else if (url.path.startsWith('/eapi/')) {
netease.crypto = 'eapi';
} else if (url.path.startsWith('/xeapi/')) {
netease.crypto = 'xeapi';
} else if (url.path.startsWith('/api/')) {
netease.crypto = 'api';
}
@ -205,6 +208,32 @@ hook.request.before = (ctx) => {
netease.e_r = false;
}
break;
case 'xeapi':
data = crypto.xeapi
.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]);
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)
@ -285,9 +314,10 @@ hook.request.after = (ctx) => {
); // for js precision
if (netease.e_r) {
// eapi's e_r is true, needs to be encrypted
// e_r is true, response body is encrypted
const decryptCrypto = netease.crypto === 'xeapi' ? crypto.xeapi : crypto.eapi;
netease.jsonBody = JSON.parse(
patch(crypto.eapi.decrypt(buffer).toString())
patch(decryptCrypto.decrypt(buffer).toString())
);
} else {
netease.jsonBody = JSON.parse(patch(buffer.toString()));