mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-clawer.git
synced 2026-03-21 09:53:10 +00:00
fix: 修复抓包不全
This commit is contained in:
parent
8c01d3fd5a
commit
bd0d1b436c
@ -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": "",
|
||||
|
||||
16
server.crt
16
server.crt
@ -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-----
|
||||
@ -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-----
|
||||
@ -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,13 +489,19 @@
|
||||
const eventSource = new EventSource('/api/events');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
// 更新状态为已连接
|
||||
updateStatus();
|
||||
|
||||
// 检查是否有新数据
|
||||
if (data.length !== capturedData.length) {
|
||||
// 检查是否有新数据(比较最后一个时间戳或数量)
|
||||
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 (hasNewData) {
|
||||
capturedData = data;
|
||||
renderCaptureList();
|
||||
|
||||
@ -504,6 +510,9 @@
|
||||
selectCapture(0);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse SSE data:', e);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function(error) {
|
||||
|
||||
221
src/index.js
Normal file
221
src/index.js
Normal 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();
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user