fix: 修复抓包不全

This commit is contained in:
ElyPrism 2026-03-13 22:23:37 +08:00
parent 8c01d3fd5a
commit bd0d1b436c
No known key found for this signature in database
6 changed files with 265 additions and 54 deletions

View File

@ -1,12 +1,10 @@
{
"name": "api-clawer",
"version": "0.0.1",
"version": "0.1.0",
"description": "网易云音乐客户端抓包工具",
"main": "src/server/app.js",
"scripts": {
"dev": "concurrently \"node src/server/app.js\" \"node src/client/app.js\"",
"start": "concurrently \"node src/server/app.js\" \"node src/client/app.js\"",
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node src/index.js"
},
"keywords": ["netease", "music", "api", "clawer"],
"author": "",

View File

@ -1,16 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICgjCCAgigAwIBAgIUafzDKFqhg8XS5YogLkI9oGsngkUwCgYIKoZIzj0EAwMw
RDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
dCBDQTEPMA0GA1UECgwGbm9ib2R5MB4XDTI1MDcyNDA4NDY1NloXDTI2MDcyNDA4
NDY1NlowezELMAkGA1UEBhMCQ04xETAPBgNVBAcMCEhhbmd6aG91MSwwKgYDVQQK
DCNOZXRFYXNlIChIYW5nemhvdSkgTmV0d29yayBDby4sIEx0ZDERMA8GA1UECwwI
SVQgRGVwdC4xGDAWBgNVBAMMDyoubXVzaWMuMTYzLmNvbTB2MBAGByqGSM49AgEG
BSuBBAAiA2IABACkvqL3b68Pv11nQVwm/9BNWGuh1fBKlqYZqnyr9Gy1xEQtf3we
WlwgVmX9rqu9nNEiAbbxIt9aGliHurKkE22+FiOKC4vduYlyyIXKlCAFXigHKr/k
6uKgc2jPfeYoC6OBgzCBgDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAg
gg1tdXNpYy4xNjMuY29tgg8qLm11c2ljLjE2My5jb20wHQYDVR0OBBYEFEeHCj4z
PzDre8No6wsWbrpmQ8hPMB8GA1UdIwQYMBaAFGW+hCo4x24AIMIYbUjR4NRlnzx1
MAoGCCqGSM49BAMDA2gAMGUCMQDD0cOUV43GSLH8bbbkw4KD6kVoknm6z6sjE5vk
ZNmgjqduSn6XzCNiiwwhRrsmtVkCMCX8uRVIIG20mdcK2YSwlSgw5mHTJsNFRnfn
x8ft2l8Tk2yZcqFSsvuVKvBwZh7bWA==
-----END CERTIFICATE-----

View File

@ -1,9 +0,0 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDDaWQTho9lbjl27ddVt9pC4req+U8iFhdMw0/RzWePrlF55fvmwRiff
GepKeBDdkx6gBwYFK4EEACKhZANiAAQApL6i92+vD79dZ0FcJv/QTVhrodXwSpam
Gap8q/RstcRELX98HlpcIFZl/a6rvZzRIgG28SLfWhpYh7qypBNtvhYjiguL3bmJ
csiFypQgBV4oByq/5OrioHNoz33mKAs=
-----END EC PRIVATE KEY-----

View File

@ -307,7 +307,7 @@
<div class="main-container">
<div class="left-panel">
<div class="header">
<h1>网易云音乐抓包工具 <span class="badge">v0.0.1</span></h1>
<h1>网易云音乐抓包工具 <span class="badge">v0.1.0</span></h1>
<p class="sub">🔍 简易网易云音乐客户端抓包工具</p>
</div>
<div class="left-content">
@ -489,20 +489,29 @@
const eventSource = new EventSource('/api/events');
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
try {
const data = JSON.parse(event.data);
// 更新状态为已连接
updateStatus();
// 更新状态为已连接
updateStatus();
// 检查是否有新数据
if (data.length !== capturedData.length) {
capturedData = data;
renderCaptureList();
// 检查是否有新数据(比较最后一个时间戳或数量)
const hasNewData = !capturedData.length ||
data.length > capturedData.length ||
(data.length > 0 && capturedData.length > 0 &&
data[data.length - 1].timestamp !== capturedData[capturedData.length - 1].timestamp);
// 如果是新数据且当前没有选中,自动选中第一个
if (selectedIndex === -1 && data.length > 0) {
selectCapture(0);
if (hasNewData) {
capturedData = data;
renderCaptureList();
// 如果是新数据且当前没有选中,自动选中第一个
if (selectedIndex === -1 && data.length > 0) {
selectCapture(0);
}
}
} catch (e) {
console.error('Failed to parse SSE data:', e);
}
};

221
src/index.js Normal file
View File

@ -0,0 +1,221 @@
// 主入口文件,同时启动 server 和 client
// 确保 Ctrl+C 时能够优雅地关闭所有服务
require('dotenv').config();
const http = require('http');
const https = require('https');
// 保存服务器引用以便关闭
let servers = [];
// 代理 server.app.js
const startServer = () => {
return new Promise((resolve, reject) => {
const packageJson = require('../package.json');
const config = require('./server/cli.js')
.program({
name: packageJson.name.replace(/@.+\//, ''),
version: packageJson.version,
})
.option(['-v', '--version'], { action: 'version' })
.option(['-p', '--port'], {
metavar: 'http[:https]',
help: 'specify server port',
})
.option(['-a', '--address'], {
metavar: 'address',
help: 'specify server host',
})
.option(['-h', '--help'], { action: 'help' })
.parse(process.argv);
global.address = config.address;
config.port = (config.port || process.env.HOOK_PORT || '9000')
.split(':')
.map((string) => parseInt(string));
const invalid = (value) => isNaN(value) || value < 1 || value > 65535;
if (config.port.some(invalid)) {
console.log('Port must be a number higher than 0 and lower than 65535.');
process.exit(1);
}
if (!process.env.PORT) {
process.env.PORT = process.env.PORT || '3000';
}
const { logScope } = require('./server/logger');
const escape = require('querystring').escape;
const hook = require('./server/hook');
const server = require('./server/server');
const logger = logScope('app');
const target = Array.from(hook.target.host);
global.port = config.port;
global.proxy = null;
global.hosts = {};
global.endpoint = 'https://music.163.com';
server.whitelist = [
'://[\w.]*music\\.126\\.net',
'://[\w.]*vod\\.126\\.net',
'://acstatic-dun.126.net',
'://[\w.]*\\.netease\\.com',
'://[\w.]*\\.163yun\\.com',
];
if (config.endpoint) server.whitelist.push(escape(config.endpoint));
const dns = (host) =>
new Promise((resolve, reject) =>
require('dns').lookup(host, { all: true }, (error, records) =>
error
? reject(error)
: resolve(records.map((record) => record.address))
)
);
Promise.all(target.map(dns))
.then((result) => {
const { host } = hook.target;
result.forEach((array) => array.forEach(host.add, host));
server.whitelist = server.whitelist.concat(
Array.from(host).map(escape)
);
const log = (type) =>
logger.info(
`${['HTTP', 'HTTPS'][type]} Server running @ http://${
address || '0.0.0.0'
}:${port[type]}`
);
if (port[0]) {
const httpServer = server.http
.listen(port[0], address)
.once('listening', () => {
log(0);
servers.push(httpServer);
});
}
if (port[1]) {
const httpsServer = server.https
.listen(port[1], address)
.once('listening', () => {
log(1);
servers.push(httpsServer);
});
}
resolve();
})
.catch((error) => {
console.log(error);
reject(error);
});
});
};
// 启动 client
const startClient = () => {
return new Promise((resolve, reject) => {
const express = require('express');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
let capturedData = [];
let clients = [];
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ limit: '10mb', extended: true }));
app.use(express.static(path.join(__dirname, 'client/public')));
app.post('/api/capture', (req, res) => {
const data = req.body;
capturedData.push(data);
console.log('Captured data:', data.path);
broadcastData();
res.status(200).send('OK');
});
app.get('/api/data', (req, res) => {
res.json(capturedData);
});
app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.write(`data: ${JSON.stringify(capturedData)}\n\n`);
clients.push(res);
req.on('close', () => {
clients = clients.filter(client => client !== res);
});
});
function broadcastData() {
clients.forEach(client => {
try {
client.write(`data: ${JSON.stringify(capturedData)}\n\n`);
} catch (e) {
clients = clients.filter(c => c !== client);
}
});
}
const clientServer = app.listen(PORT, () => {
console.log(`Frontend server running at http://localhost:${PORT}`);
servers.push(clientServer);
resolve();
});
clientServer.on('error', reject);
});
};
// 优雅关闭所有服务器
const gracefulShutdown = async (signal) => {
console.log(`\n收到 ${signal} 信号,正在关闭服务器...`);
// 关闭所有服务器
const closePromises = servers.map(server => {
return new Promise((resolve) => {
server.close(() => {
console.log('服务器已关闭');
resolve();
});
// 设置超时,强制关闭
setTimeout(() => {
resolve();
}, 5000);
});
});
await Promise.all(closePromises);
console.log('所有服务已关闭');
process.exit(0);
};
// 监听退出信号
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
// 启动所有服务
const startAll = async () => {
try {
await Promise.all([
startServer(),
startClient()
]);
console.log('所有服务启动完成!');
} catch (error) {
console.error('启动服务失败:', error);
process.exit(1);
}
};
startAll();

View File

@ -271,11 +271,6 @@ hook.request.after = (ctx) => {
proxyRes.statusCode === 200
)
proxyRes.statusCode = 206;
if (
netease &&
hook.target.path.has(netease.path) &&
proxyRes.statusCode === 200
) {
return request
.read(proxyRes, true)
.then((buffer) =>
@ -297,24 +292,37 @@ hook.request.after = (ctx) => {
netease.jsonBody = JSON.parse(patch(buffer.toString()));
}
// Send data to frontend
// Send data to frontend for all captured requests
const dataToSend = {
timestamp: new Date().toISOString(),
path: netease.path,
param: netease.param,
response: netease.jsonBody
response: netease.jsonBody,
statusCode: proxyRes.statusCode
};
axios.post(`http://localhost:${process.env.PORT || 3000}/api/capture`, dataToSend)
.catch(err => logger.error('Failed to send data to frontend:', err));
})
.catch(
(error) =>
error &&
.catch((error) => {
// 即使读取响应体失败,也发送基本信息到前端
const dataToSend = {
timestamp: new Date().toISOString(),
path: netease.path,
param: netease.param,
response: null,
statusCode: proxyRes.statusCode,
error: error.message
};
axios.post(`http://localhost:${process.env.PORT || 3000}/api/capture`, dataToSend)
.catch(err => logger.error('Failed to send data to frontend:', err));
if (error) {
logger.error(
error,
`A error occurred in hook.request.after when hooking ${req.url}.`
)
);
);
}
});
} else if (pkg) {
if (new Set([201, 301, 302, 303, 307, 308]).has(proxyRes.statusCode)) {
return request(