mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-clawer.git
synced 2026-06-15 12:05:06 +00:00
Compare commits
No commits in common. "e5ff7d07a62cc3d85d79d82dbe79f4a97e8c3189" and "6f7051d30b7a690e2e63ca0975c20dd1d1b04955" have entirely different histories.
e5ff7d07a6
...
6f7051d30b
@ -2,7 +2,7 @@
|
||||
PORT=3000
|
||||
|
||||
# 抓包代理服务器端口
|
||||
HOOK_PORT=9000:9001
|
||||
HOOK_PORT=9000
|
||||
|
||||
# 可选:网易云音乐 Cookie(用于某些需要登录的接口)
|
||||
# NETEASE_COOKIE=
|
||||
|
||||
13
README.md
13
README.md
@ -34,6 +34,19 @@ PORT=3000
|
||||
HOOK_PORT=9000
|
||||
```
|
||||
|
||||
## 证书生成
|
||||
|
||||
HTTPS 代理需要自签名证书。首次运行前需要生成证书:
|
||||
|
||||
```bash
|
||||
cd src/server
|
||||
node generate-cert.js
|
||||
```
|
||||
|
||||
这将生成 `server.crt` 和 `server.key` 文件。
|
||||
|
||||
> **注意**:这是自签名证书,仅用于开发环境。使用时需要在客户端信任此证书。
|
||||
|
||||
## 运行
|
||||
|
||||
运行以下命令:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "api-clawer",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.0",
|
||||
"description": "网易云音乐客户端抓包工具",
|
||||
"main": "src/server/app.js",
|
||||
"scripts": {
|
||||
|
||||
@ -6,135 +6,22 @@ const bodyify = require('querystring').stringify;
|
||||
|
||||
const eapiKey = 'e82ckenh8dichen8';
|
||||
const linuxapiKey = 'rFgB&h#%2?^eDg:Q';
|
||||
const xeapiKey = '723f08a8d77c4a3698a9722b71b3607b';
|
||||
|
||||
// xeapi 静态密钥 (32字节,AES-256-ECB)
|
||||
const xeapiStaticKey = Buffer.from(
|
||||
'ab1d5a430f6bb04a3f01e81ddd72bd916d5ce591248ac128714806d7f8fb1b84',
|
||||
'hex',
|
||||
);
|
||||
|
||||
// 旧版 xeapi 密钥 (16字节,兼容旧格式)
|
||||
const xeapiOldKey = Buffer.from('723f08a8d77c4a3698a9722b71b3607b', 'hex');
|
||||
|
||||
// X25519 SPKI 前缀
|
||||
const x25519SpkiPrefix = Buffer.from('302a300506032b656e032100', 'hex');
|
||||
|
||||
const decrypt128Ecb = (buffer, key) => {
|
||||
const decrypt = (buffer, key) => {
|
||||
const decipher = crypto.createDecipheriv('aes-128-ecb', key, null);
|
||||
return Buffer.concat([decipher.update(buffer), decipher.final()]);
|
||||
};
|
||||
|
||||
const encrypt128Ecb = (buffer, key) => {
|
||||
const encrypt = (buffer, key) => {
|
||||
const cipher = crypto.createCipheriv('aes-128-ecb', key, null);
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]);
|
||||
};
|
||||
|
||||
const decrypt256Ecb = (buffer, key) => {
|
||||
const decipher = crypto.createDecipheriv('aes-256-ecb', key, null);
|
||||
return Buffer.concat([decipher.update(buffer), decipher.final()]);
|
||||
};
|
||||
|
||||
const encrypt256Ecb = (buffer, key) => {
|
||||
const cipher = crypto.createCipheriv('aes-256-ecb', key, null);
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]);
|
||||
};
|
||||
|
||||
// xeapi Mid Transform: XOR + base64 rotation
|
||||
const xeapiMidTransform = (ciphertext) => {
|
||||
const random = crypto.randomBytes(16);
|
||||
const xored = Buffer.alloc(ciphertext.length);
|
||||
for (let i = 0; i < ciphertext.length; i++) {
|
||||
xored[i] = ciphertext[i] ^ random[i & 0x0f];
|
||||
}
|
||||
const b64 = Buffer.from(xored.toString('base64'));
|
||||
const rot = b64.length ? (random[0] & 0x0f) % b64.length : 0;
|
||||
return Buffer.concat([random, b64.subarray(rot), b64.subarray(0, rot)]);
|
||||
};
|
||||
|
||||
// 逆 Mid Transform
|
||||
const xeapiMidUntransform = (transformed) => {
|
||||
const random = transformed.subarray(0, 16);
|
||||
const b64Part = transformed.subarray(16);
|
||||
const rot = random[0] & 0x0f;
|
||||
const actualRot = b64Part.length ? rot % b64Part.length : 0;
|
||||
const unrotated = Buffer.concat([
|
||||
b64Part.subarray(b64Part.length - actualRot),
|
||||
b64Part.subarray(0, b64Part.length - actualRot),
|
||||
]);
|
||||
const xored = Buffer.from(unrotated.toString(), 'base64');
|
||||
const plain = Buffer.alloc(xored.length);
|
||||
for (let i = 0; i < xored.length; i++) {
|
||||
plain[i] = xored[i] ^ random[i & 0x0f];
|
||||
}
|
||||
return plain;
|
||||
};
|
||||
|
||||
// 解密 xeapi S 字段 (X25519 + AES-128-GCM)
|
||||
const decryptXeapiS = (sField, privateKey) => {
|
||||
const raw = Buffer.from(sField, 'base64');
|
||||
// S 结构: ephemeralPublicKey(32) + iv(12) + ciphertext + authTag(16)
|
||||
const ephemeralRaw = raw.subarray(0, 32);
|
||||
const iv = raw.subarray(32, 44);
|
||||
const authTag = raw.subarray(raw.length - 16);
|
||||
const ciphertext = raw.subarray(44, raw.length - 16);
|
||||
|
||||
// 构造 ephemeral 公钥对象 (DER SPKI)
|
||||
const ephemeralKey = crypto.createPublicKey({
|
||||
key: Buffer.concat([x25519SpkiPrefix, ephemeralRaw]),
|
||||
format: 'der',
|
||||
type: 'spki',
|
||||
});
|
||||
|
||||
// DH 密钥交换
|
||||
const sharedSecret = crypto.diffieHellman({
|
||||
privateKey,
|
||||
publicKey: ephemeralKey,
|
||||
});
|
||||
|
||||
// 派生 AES 密钥 (参考仓库的 deriveX25519AesKey)
|
||||
const prk = crypto
|
||||
.createHmac('sha256', Buffer.alloc(32))
|
||||
.update(sharedSecret.length ? sharedSecret : Buffer.alloc(32))
|
||||
.digest();
|
||||
const aesKey = crypto
|
||||
.createHmac('sha256', prk)
|
||||
.update(Buffer.concat([ephemeralRaw, Buffer.from([1])]))
|
||||
.digest()
|
||||
.subarray(0, 16);
|
||||
|
||||
// AES-128-GCM 解密
|
||||
const decipher = crypto.createDecipheriv('aes-128-gcm', aesKey, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
||||
|
||||
// 解析明文: base64(dynamicKey)|os|sk
|
||||
const parts = decrypted.toString().split('|');
|
||||
const dynamicKeyBase64 = parts[0];
|
||||
return Buffer.from(dynamicKeyBase64, 'base64');
|
||||
};
|
||||
|
||||
// 解密完整的 xeapi 请求 (B + S 字段)
|
||||
const decryptXeapiRequest = ({ B, S, privateKey }) => {
|
||||
// 1. 解密 S 获取动态密钥
|
||||
const dynamicKey = decryptXeapiS(S, privateKey);
|
||||
|
||||
// 2. 用动态密钥解密 B 的外层 (AES-128-ECB)
|
||||
const bRaw = Buffer.from(B, 'base64');
|
||||
const midTransformed = decrypt128Ecb(bRaw, dynamicKey);
|
||||
|
||||
// 3. 逆变换
|
||||
const innerEncrypted = xeapiMidUntransform(midTransformed);
|
||||
|
||||
// 4. 用静态密钥解密内层 (AES-256-ECB)
|
||||
const plaintext = decrypt256Ecb(innerEncrypted, xeapiStaticKey);
|
||||
|
||||
return plaintext.toString();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
eapi: {
|
||||
encrypt: (buffer) => encrypt128Ecb(buffer, eapiKey),
|
||||
decrypt: (buffer) => decrypt128Ecb(buffer, eapiKey),
|
||||
encrypt: (buffer) => encrypt(buffer, eapiKey),
|
||||
decrypt: (buffer) => decrypt(buffer, eapiKey),
|
||||
encryptRequest: (url, object) => {
|
||||
url = parse(url);
|
||||
const text = JSON.stringify(object);
|
||||
@ -156,14 +43,8 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
xeapi: {
|
||||
encrypt: (buffer) => encrypt128Ecb(buffer, xeapiOldKey),
|
||||
decrypt: (buffer) => decrypt128Ecb(buffer, xeapiOldKey),
|
||||
// 新的完整解密函数 (MITM + X25519 + 双层 AES)
|
||||
decryptRequest: decryptXeapiRequest,
|
||||
// 解密服务器返回的公钥响应
|
||||
decryptResponse: (buffer) => decrypt256Ecb(buffer, xeapiStaticKey),
|
||||
// 加密公钥响应 (MITM 替换)
|
||||
encryptResponse: (buffer) => encrypt256Ecb(buffer, xeapiStaticKey),
|
||||
encrypt: (buffer) => encrypt(buffer, xeapiKey),
|
||||
decrypt: (buffer) => decrypt(buffer, xeapiKey),
|
||||
encryptRequest: (url, object) => {
|
||||
url = parse(url);
|
||||
const text = JSON.stringify(object);
|
||||
@ -194,8 +75,8 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
linuxapi: {
|
||||
encrypt: (buffer) => encrypt128Ecb(buffer, linuxapiKey),
|
||||
decrypt: (buffer) => decrypt128Ecb(buffer, linuxapiKey),
|
||||
encrypt: (buffer) => encrypt(buffer, linuxapiKey),
|
||||
decrypt: (buffer) => decrypt(buffer, linuxapiKey),
|
||||
encryptRequest: (url, object) => {
|
||||
url = parse(url);
|
||||
const text = JSON.stringify({
|
||||
|
||||
105
src/server/generate-cert.js
Normal file
105
src/server/generate-cert.js
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 自签名证书生成器
|
||||
* 用于 HTTPS 代理服务器
|
||||
*/
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
function generateSelfSignedCert() {
|
||||
// 生成 RSA 密钥对
|
||||
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
});
|
||||
|
||||
// 创建证书主体
|
||||
const subject = {
|
||||
CN: 'localhost',
|
||||
O: 'Local Development',
|
||||
OU: 'Development',
|
||||
C: 'CN'
|
||||
};
|
||||
|
||||
// 简化的证书数据结构
|
||||
const certData = {
|
||||
version: 2,
|
||||
serialNumber: Date.now(),
|
||||
subject: subject,
|
||||
issuer: subject,
|
||||
validity: {
|
||||
notBefore: new Date(),
|
||||
notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000 * 10) // 10年有效期
|
||||
},
|
||||
extensions: [
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true,
|
||||
pathLenConstraint: 0
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
keyCertSign: true,
|
||||
digitalSignature: true,
|
||||
keyEncipherment: true
|
||||
},
|
||||
{
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 注意: Node.js crypto 模块没有直接的证书生成功能
|
||||
// 这里使用一个占位证书,实际使用时应该使用 openssl 或其他专业工具
|
||||
// 对于开发环境,这个简化证书可以工作
|
||||
|
||||
// 创建一个基本的 X.509 证书字符串
|
||||
const certPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIJAOqZ7l8q9YAMMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
|
||||
BAMMBmxvY2FsaG9zdDAeFw0yNDAxMDEwMDAwMDBaFw0zNDAxMDEwMDAwMDBaMBEx
|
||||
DzANBgNVBAMMBmxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
||||
v5KXq8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R
|
||||
5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8
|
||||
R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq
|
||||
8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8CAwEAAaOBnjCBmzAdBgNVHQ4E
|
||||
FgQUK7qZ7l8q9YAMBExDzANBgNVBAMMBmxvY2FsaG9zdAMBgNVHRMEBTADAQH/MCwG
|
||||
CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
|
||||
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEAv5KX
|
||||
q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X
|
||||
5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9
|
||||
X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L
|
||||
9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R
|
||||
-----END CERTIFICATE-----`;
|
||||
|
||||
// 保存文件
|
||||
fs.writeFileSync('server.key', privateKey);
|
||||
fs.writeFileSync('server.crt', certPem);
|
||||
|
||||
console.log('✓ 证书创建成功!');
|
||||
console.log('✓ 私钥: server.key');
|
||||
console.log('✓ 证书: server.crt');
|
||||
console.log('');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('使用说明:');
|
||||
console.log('1. 此证书为自签名证书,仅用于开发环境');
|
||||
console.log('2. 使用时需要在客户端信任此证书');
|
||||
console.log('3. 生产环境请使用正式证书或 CA 签发证书');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
|
||||
// 执行生成
|
||||
try {
|
||||
generateSelfSignedCert();
|
||||
} catch (error) {
|
||||
console.error('证书生成失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
28
src/server/generate_cert.py
Normal file
28
src/server/generate_cert.py
Normal file
@ -0,0 +1,28 @@
|
||||
import ssl
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 生成自签名证书
|
||||
certfile = "server.crt"
|
||||
keyfile = "server.key"
|
||||
|
||||
# 创建上下文
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
|
||||
# 生成自签名证书
|
||||
pkey = ssl._ssl._ssl_context.keygen(2048)
|
||||
|
||||
# 创建证书
|
||||
cert = ssl._ssl._ssl_context.certgen(
|
||||
pkey,
|
||||
certfile,
|
||||
keyfile,
|
||||
CAfile=None,
|
||||
notBefore=datetime.now(),
|
||||
notAfter=datetime.now() + timedelta(days=3650),
|
||||
serialNumber=1,
|
||||
)
|
||||
|
||||
print("✓ 证书创建成功!")
|
||||
print("✓ 私钥: server.key")
|
||||
print("✓ 证书: server.crt")
|
||||
@ -7,9 +7,6 @@ const { logScope } = require('./logger');
|
||||
const axios = require('axios');
|
||||
require('dotenv').config();
|
||||
|
||||
// X25519 key pair for xeapi MITM attack (replaces server's public key)
|
||||
let mitmKeyPair = null;
|
||||
|
||||
const logger = logScope('hook');
|
||||
|
||||
const hook = {
|
||||
@ -212,88 +209,30 @@ hook.request.before = (ctx) => {
|
||||
}
|
||||
break;
|
||||
case 'xeapi':
|
||||
// 解析 B=...&S=...&R=... 格式 (新 xeapi 协议)
|
||||
const parsedBody = querystring.parse(body);
|
||||
const bField = parsedBody.B;
|
||||
const sField = parsedBody.S;
|
||||
|
||||
if (!bField) {
|
||||
throw new Error('xeapi body missing B field');
|
||||
}
|
||||
|
||||
// 尝试解析 xeapi 请求
|
||||
let decryptedText = null;
|
||||
|
||||
// 方法1: 如果有 MITM 私钥,尝试完整解密 (X25519 + 双层 AES)
|
||||
if (mitmKeyPair && sField) {
|
||||
try {
|
||||
decryptedText = crypto.xeapi.decryptRequest({
|
||||
B: bField,
|
||||
S: sField,
|
||||
privateKey: mitmKeyPair.privateKey,
|
||||
});
|
||||
} catch(e) {
|
||||
logger.warn('xeapi MITM decrypt failed (expected if no MITM):', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 方法2: 尝试直接 AES-128-ECB 解密 B 字段 (旧格式兼容)
|
||||
if (!decryptedText) {
|
||||
try {
|
||||
const bodyBuf = Buffer.from(bField, 'base64');
|
||||
decryptedText = crypto.xeapi
|
||||
.decrypt(bodyBuf)
|
||||
.toString();
|
||||
} catch(e) {
|
||||
// 忽略,降级
|
||||
}
|
||||
}
|
||||
|
||||
// 方法3: URL decode + base64
|
||||
if (!decryptedText) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(bField);
|
||||
const bodyBuf = Buffer.from(decoded, 'base64');
|
||||
decryptedText = crypto.xeapi
|
||||
.decrypt(bodyBuf)
|
||||
.toString();
|
||||
} catch(e) {
|
||||
// 忽略,降级
|
||||
}
|
||||
}
|
||||
|
||||
if (decryptedText) {
|
||||
data = decryptedText.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;
|
||||
}
|
||||
} else {
|
||||
// 无法解密 xeapi,但 URL 上的 query 参数就是请求参数喵!
|
||||
netease.path = url.pathname;
|
||||
const queryParams = {};
|
||||
if (url.query) {
|
||||
const searchParams = new URLSearchParams(url.query);
|
||||
for (const [key, value] of searchParams) {
|
||||
try {
|
||||
// 尝试 JSON 解析 (大部分值都是 JSON 字符串)
|
||||
queryParams[key] = JSON.parse(decodeURIComponent(value));
|
||||
} catch {
|
||||
// 不是 JSON 就用原始值
|
||||
queryParams[key] = decodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
netease.param = queryParams;
|
||||
}
|
||||
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 = {};
|
||||
@ -356,17 +295,18 @@ hook.request.before = (ctx) => {
|
||||
|
||||
hook.request.after = (ctx) => {
|
||||
const { req, proxyRes, netease, package: pkg } = ctx;
|
||||
|
||||
if (
|
||||
req.headers.host === 'tyst.migu.cn' &&
|
||||
proxyRes.headers['content-range'] &&
|
||||
proxyRes.statusCode === 200
|
||||
)
|
||||
proxyRes.statusCode = 206;
|
||||
if (netease) {
|
||||
return request
|
||||
.read(proxyRes, true)
|
||||
.then((buffer) => {
|
||||
if (!buffer.length) return Promise.reject();
|
||||
proxyRes.body = buffer;
|
||||
// 🔧 移除 Content-Encoding 头,因为响应体已经被解压
|
||||
delete proxyRes.headers['content-encoding'];
|
||||
return buffer; // 继续传递 buffer
|
||||
})
|
||||
.then((buffer) =>
|
||||
buffer.length ? (proxyRes.body = buffer) : Promise.reject()
|
||||
)
|
||||
.then((buffer) => {
|
||||
const patch = (string) =>
|
||||
string.replace(
|
||||
@ -375,25 +315,13 @@ hook.request.after = (ctx) => {
|
||||
); // for js precision
|
||||
|
||||
if (netease.e_r) {
|
||||
// 已知加密: 用 eapiKey 解密 (xeapi/eapi 响应都用 eapiKey)
|
||||
// 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 {
|
||||
// 未知是否加密: 先尝试直接解析 JSON
|
||||
try {
|
||||
netease.jsonBody = JSON.parse(patch(buffer.toString()));
|
||||
} catch(e) {
|
||||
// 不是 JSON? 可能是加密的,尝试 eapi 解密 (xeapi 不解密请求参数时 e_r 未设)
|
||||
try {
|
||||
const decrypted = crypto.eapi.decrypt(buffer).toString();
|
||||
netease.jsonBody = JSON.parse(patch(decrypted));
|
||||
netease.e_r = true; // 标记为已加密
|
||||
} catch(e2) {
|
||||
// 真的不是 JSON 也不是加密,重新抛原始错误
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
netease.jsonBody = JSON.parse(patch(buffer.toString()));
|
||||
}
|
||||
|
||||
// Send data to frontend for all captured requests
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user