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
d745ec710f
commit
8c01d3fd5a
14
ca.crt
Normal file
14
ca.crt
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICGjCCAaCgAwIBAgIUXBIOR4IUZCnxKkT2yr8ZdS17y0wwCgYIKoZIzj0EAwMw
|
||||
RDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTEPMA0GA1UECgwGbm9ib2R5MB4XDTIyMDQwNDEyMDk1MloXDTI3MDQwMzEy
|
||||
MDk1MlowRDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVz
|
||||
aWMgUm9vdCBDQTEPMA0GA1UECgwGbm9ib2R5MHYwEAYHKoZIzj0CAQYFK4EEACID
|
||||
YgAEZDE7xoQc0jylGpqesuWZKdSBAxD1kydeYRfJr296Pc2v32P1q2VQFBC8XY7d
|
||||
nzfKN5zLEc9csWRsvmpwXVlXq2wJW/sJENjErQ8yeaw/4alxDUWHwwgiu/ienBl/
|
||||
iIMWo1MwUTAdBgNVHQ4EFgQUZb6EKjjHbgAgwhhtSNHg1GWfPHUwHwYDVR0jBBgw
|
||||
FoAUZb6EKjjHbgAgwhhtSNHg1GWfPHUwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjO
|
||||
PQQDAwNoADBlAjEAuzn+fWQpEyFIUuQZC8TcfteCEvn2KettQT2ZOfQxHB3sIWhE
|
||||
BC9cebRgdc74Lvv6AjBLQyq2SfDzrDE0PexyJxy8IQHAncP0+1QnAXVU5GO6YJYg
|
||||
1gYZwdmbNOhh81S21l8=
|
||||
-----END CERTIFICATE-----
|
||||
16
server.crt
Normal file
16
server.crt
Normal file
@ -0,0 +1,16 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICgjCCAgigAwIBAgIUafzDKFqhg8XS5YogLkI9oGsngkUwCgYIKoZIzj0EAwMw
|
||||
RDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTEPMA0GA1UECgwGbm9ib2R5MB4XDTI1MDcyNDA4NDY1NloXDTI2MDcyNDA4
|
||||
NDY1NlowezELMAkGA1UEBhMCQ04xETAPBgNVBAcMCEhhbmd6aG91MSwwKgYDVQQK
|
||||
DCNOZXRFYXNlIChIYW5nemhvdSkgTmV0d29yayBDby4sIEx0ZDERMA8GA1UECwwI
|
||||
SVQgRGVwdC4xGDAWBgNVBAMMDyoubXVzaWMuMTYzLmNvbTB2MBAGByqGSM49AgEG
|
||||
BSuBBAAiA2IABACkvqL3b68Pv11nQVwm/9BNWGuh1fBKlqYZqnyr9Gy1xEQtf3we
|
||||
WlwgVmX9rqu9nNEiAbbxIt9aGliHurKkE22+FiOKC4vduYlyyIXKlCAFXigHKr/k
|
||||
6uKgc2jPfeYoC6OBgzCBgDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAg
|
||||
gg1tdXNpYy4xNjMuY29tgg8qLm11c2ljLjE2My5jb20wHQYDVR0OBBYEFEeHCj4z
|
||||
PzDre8No6wsWbrpmQ8hPMB8GA1UdIwQYMBaAFGW+hCo4x24AIMIYbUjR4NRlnzx1
|
||||
MAoGCCqGSM49BAMDA2gAMGUCMQDD0cOUV43GSLH8bbbkw4KD6kVoknm6z6sjE5vk
|
||||
ZNmgjqduSn6XzCNiiwwhRrsmtVkCMCX8uRVIIG20mdcK2YSwlSgw5mHTJsNFRnfn
|
||||
x8ft2l8Tk2yZcqFSsvuVKvBwZh7bWA==
|
||||
-----END CERTIFICATE-----
|
||||
9
server.key
Normal file
9
server.key
Normal file
@ -0,0 +1,9 @@
|
||||
-----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-----
|
||||
@ -6,6 +6,7 @@ const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
let capturedData = [];
|
||||
let clients = [];
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
@ -14,6 +15,10 @@ app.post('/api/capture', (req, res) => {
|
||||
const data = req.body;
|
||||
capturedData.push(data);
|
||||
console.log('Captured data:', data.path);
|
||||
|
||||
// 通知所有 SSE 客户端有新数据
|
||||
broadcastData();
|
||||
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
|
||||
@ -21,6 +26,34 @@ app.get('/api/data', (req, res) => {
|
||||
res.json(capturedData);
|
||||
});
|
||||
|
||||
// SSE 端点
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Frontend server running at http://localhost:${PORT}`);
|
||||
});
|
||||
@ -17,93 +17,340 @@
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--fg); background: var(--bg); line-height: 1.6; }
|
||||
.container { max-width: 960px; margin: 40px auto; padding: 0 20px; }
|
||||
@media (max-width: 480px) {
|
||||
.container { margin: 20px auto; padding: 0 16px; }
|
||||
header.site-header h1 { font-size: 22px; }
|
||||
.block { padding: 16px; }
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
header.site-header { margin-bottom: 24px; }
|
||||
header.site-header h1 { font-size: 28px; font-weight: 600; margin: 0; color: var(--accent); }
|
||||
.badge { display: inline-block; margin-left: 8px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 12px; font-size: 12px; color: var(--muted); }
|
||||
.sub { margin-top: 8px; color: var(--muted); font-size: 14px; }
|
||||
.block { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); }
|
||||
.block h2 { margin: 0 0 12px; font-size: 18px; font-weight: 600; }
|
||||
.kvs { display: grid; grid-template-columns: 100px 1fr; gap: 8px 12px; align-items: start; }
|
||||
.kvs div:first-child { color: var(--muted); flex-shrink: 0; }
|
||||
.kvs div:last-child { word-break: break-all; overflow-wrap: anywhere; min-width: 0; overflow: hidden; }
|
||||
@media (max-width: 480px) {
|
||||
.kvs { grid-template-columns: 1fr; gap: 4px 12px; }
|
||||
.kvs div:first-child { font-weight: 500; }
|
||||
|
||||
.left-panel {
|
||||
width: 33%;
|
||||
min-width: 300px;
|
||||
max-width: 450px;
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.capture-item { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 12px; overflow: hidden; }
|
||||
.capture-header { background: #fafafa; padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
|
||||
.capture-path { font-weight: 600; color: var(--accent); }
|
||||
.capture-time { font-size: 12px; color: var(--muted); }
|
||||
.capture-body { padding: 16px; }
|
||||
.capture-section { margin-bottom: 12px; }
|
||||
.capture-section:last-child { margin-bottom: 0; }
|
||||
.capture-section strong { display: block; margin-bottom: 6px; font-size: 14px; color: var(--muted); }
|
||||
pre { margin: 0; background: #f9f9f9; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; font-size: 13px; }
|
||||
code { font-family: 'Courier New', monospace; font-size: 13px; }
|
||||
@media (max-width: 480px) {
|
||||
code { font-size: 12px; }
|
||||
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
background: var(--bg);
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.left-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #fafafa;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.block {
|
||||
background: #fafafa;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.block h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.kvs {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
gap: 6px 10px;
|
||||
align-items: start;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kvs div:first-child {
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kvs div:last-child {
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background: #e6f4ea;
|
||||
color: #1e8e3e;
|
||||
}
|
||||
|
||||
.status-waiting {
|
||||
background: #fef7e0;
|
||||
color: #f9ab00;
|
||||
}
|
||||
|
||||
.capture-list {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.capture-list h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.capture-list-item {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.capture-list-item:hover {
|
||||
border-color: var(--accent);
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.capture-list-item.active {
|
||||
border-color: var(--accent);
|
||||
background: #fff5f5;
|
||||
box-shadow: 0 2px 4px rgba(194, 12, 12, 0.1);
|
||||
}
|
||||
|
||||
.capture-list-path {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.capture-list-time {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.empty-list .icon {
|
||||
font-size: 36px;
|
||||
margin-bottom: 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.detail-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.detail-section strong {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #a00a0a;
|
||||
}
|
||||
|
||||
.copy-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 14px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.no-selection .icon {
|
||||
font-size: 48px;
|
||||
margin-right: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
|
||||
.status-connected { background: #e6f4ea; color: #1e8e3e; }
|
||||
.status-waiting { background: #fef7e0; color: #f9ab00; }
|
||||
.no-data { text-align: center; padding: 40px 20px; color: var(--muted); }
|
||||
.no-data .icon { font-size: 48px; margin-bottom: 12px; opacity: 0.5; }
|
||||
footer.site-footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--muted); text-align: center; }
|
||||
footer.site-footer a { color: var(--fg); text-decoration: none; transition: color 0.2s ease; }
|
||||
footer.site-footer a:hover { color: var(--accent); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="site-header">
|
||||
<h1>网易云音乐抓包工具 <span class="badge">v0.0.1</span></h1>
|
||||
<p class="sub">🔍 简易网易云音乐客户端抓包工具,适用于贡献 NeteaseCloudMusicApi 项目</p>
|
||||
</header>
|
||||
|
||||
<section class="block">
|
||||
<h2>状态</h2>
|
||||
<div class="kvs">
|
||||
<div>连接状态</div><div><span id="status" class="status status-waiting">等待连接...</span></div>
|
||||
<div>Base URL</div><div id="base-url">—</div>
|
||||
<div>当前页</div><div id="current-url">—</div>
|
||||
<div>抓包数量</div><div id="capture-count">0</div>
|
||||
<div class="main-container">
|
||||
<div class="left-panel">
|
||||
<div class="header">
|
||||
<h1>网易云音乐抓包工具 <span class="badge">v0.0.1</span></h1>
|
||||
<p class="sub">🔍 简易网易云音乐客户端抓包工具</p>
|
||||
</div>
|
||||
</section>
|
||||
<div class="left-content">
|
||||
<section class="block">
|
||||
<h2>状态</h2>
|
||||
<div class="kvs">
|
||||
<div>连接状态</div><div><span id="status" class="status status-waiting">等待连接...</span></div>
|
||||
<div>Base URL</div><div id="base-url">—</div>
|
||||
<div>抓包数量</div><div id="capture-count">0</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<h2>使用说明</h2>
|
||||
<div class="kvs">
|
||||
<div>1. 启动服务</div><div>运行 <code>npm run dev</code> 启动抓包和前端服务</div>
|
||||
<div>2. 设置代理</div><div>将网易云音乐客户端代理设置为 <code>http://localhost:9000</code></div>
|
||||
<div>3. 开始抓包</div><div>在网易云音乐客户端中进行操作,抓包数据将实时显示</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="block">
|
||||
<h2>使用说明</h2>
|
||||
<div class="kvs">
|
||||
<div>1. 启动服务</div><div>运行 <code>npm run dev</code></div>
|
||||
<div>2. 设置代理</div><div>客户端代理: <code>localhost:9000</code></div>
|
||||
<div>3. 开始抓包</div><div>在客户端中操作,数据实时显示</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<h2>抓包数据</h2>
|
||||
<div id="data">
|
||||
<div class="no-data">
|
||||
<div class="icon">📡</div>
|
||||
<p>暂无抓包数据</p>
|
||||
<p style="font-size: 12px; margin-top: 8px;">请设置网易云音乐客户端代理并开始使用</p>
|
||||
<div class="capture-list">
|
||||
<h2>抓包列表</h2>
|
||||
<div id="capture-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div id="detail-panel" class="detail-panel">
|
||||
<div class="no-selection">
|
||||
<span class="icon">📡</span>
|
||||
<span>请从左侧选择一个抓包记录查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="site-footer">
|
||||
<a href="https://github.com/NeteaseCloudMusicApiEnhanced/api-clawer" target="_blank">GitHub</a>
|
||||
</footer>
|
||||
</main>
|
||||
<div id="toast" class="toast">已复制到剪贴板</div>
|
||||
|
||||
<script>
|
||||
const DATA_ENDPOINT = '/api/data';
|
||||
let lastDataCount = 0;
|
||||
let capturedData = [];
|
||||
let selectedIndex = -1;
|
||||
|
||||
function updateStatus() {
|
||||
const statusEl = document.getElementById('status');
|
||||
@ -111,59 +358,108 @@
|
||||
statusEl.textContent = '已连接';
|
||||
}
|
||||
|
||||
function displayData(data) {
|
||||
const container = document.getElementById('data');
|
||||
function renderCaptureList() {
|
||||
const container = document.getElementById('capture-list');
|
||||
const countEl = document.getElementById('capture-count');
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
if (!capturedData || capturedData.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="no-data">
|
||||
<div class="empty-list">
|
||||
<div class="icon">📡</div>
|
||||
<p>暂无抓包数据</p>
|
||||
<p style="font-size: 12px; margin-top: 8px;">请设置网易云音乐客户端代理并开始使用</p>
|
||||
<p style="font-size: 12px; margin-top: 6px; opacity: 0.7;">请设置网易云音乐客户端代理并开始使用</p>
|
||||
</div>
|
||||
`;
|
||||
countEl.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是新数据,更新显示
|
||||
if (data.length !== lastDataCount) {
|
||||
lastDataCount = data.length;
|
||||
countEl.textContent = data.length;
|
||||
|
||||
container.innerHTML = data.map(item => `
|
||||
<div class="capture-item">
|
||||
<div class="capture-header">
|
||||
<span class="capture-path">${escapeHtml(item.path)}</span>
|
||||
<span class="capture-time">${formatTime(item.timestamp)}</span>
|
||||
</div>
|
||||
<div class="capture-body">
|
||||
<div class="capture-section">
|
||||
<strong>请求参数:</strong>
|
||||
<pre><code>${formatJson(item.param)}</code></pre>
|
||||
</div>
|
||||
${item.response ? `
|
||||
<div class="capture-section">
|
||||
<strong>响应数据:</strong>
|
||||
<pre><code>${formatJson(item.response)}</code></pre>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
countEl.textContent = capturedData.length;
|
||||
|
||||
container.innerHTML = capturedData.map((item, index) => `
|
||||
<div class="capture-list-item ${index === selectedIndex ? 'active' : ''}"
|
||||
onclick="selectCapture(${index})">
|
||||
<div class="capture-list-path">${escapeHtml(item.path)}</div>
|
||||
<div class="capture-list-time">${formatTime(item.timestamp)}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const response = await fetch(DATA_ENDPOINT);
|
||||
const data = await response.json();
|
||||
displayData(data);
|
||||
updateStatus();
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
function selectCapture(index) {
|
||||
selectedIndex = index;
|
||||
const item = capturedData[index];
|
||||
const detailPanel = document.getElementById('detail-panel');
|
||||
|
||||
if (!item) {
|
||||
detailPanel.innerHTML = `
|
||||
<div class="no-selection">
|
||||
<span class="icon">📡</span>
|
||||
<span>请从左侧选择一个抓包记录查看详情</span>
|
||||
</div>
|
||||
`;
|
||||
renderCaptureList();
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredParam = filterParam(item.param);
|
||||
|
||||
detailPanel.innerHTML = `
|
||||
<div class="detail-header">
|
||||
<h2>${escapeHtml(item.path)}</h2>
|
||||
<div class="detail-time">${formatTime(item.timestamp)}</div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<strong>
|
||||
请求参数
|
||||
<button class="copy-btn" onclick="copyParam(${index})">复制参数</button>
|
||||
</strong>
|
||||
<pre><code>${formatJson(filteredParam)}</code></pre>
|
||||
</div>
|
||||
${item.response ? `
|
||||
<div class="detail-section">
|
||||
<strong>响应数据</strong>
|
||||
<pre><code>${formatJson(item.response)}</code></pre>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
renderCaptureList();
|
||||
}
|
||||
|
||||
function filterParam(param) {
|
||||
if (!param || typeof param !== 'object') return param;
|
||||
|
||||
const filtered = {};
|
||||
for (const key in param) {
|
||||
if (key !== 'e_r' && key !== 'header') {
|
||||
filtered[key] = param[key];
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function copyParam(index) {
|
||||
const item = capturedData[index];
|
||||
if (!item || !item.param) return;
|
||||
|
||||
const filteredParam = filterParam(item.param);
|
||||
const jsonStr = formatJson(filteredParam);
|
||||
|
||||
navigator.clipboard.writeText(jsonStr).then(() => {
|
||||
showToast('已复制到剪贴板');
|
||||
}).catch(err => {
|
||||
console.error('Copy failed:', err);
|
||||
showToast('复制失败,请手动复制');
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.textContent = message;
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function formatJson(obj) {
|
||||
@ -189,16 +485,38 @@
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function setupSSE() {
|
||||
const eventSource = new EventSource('/api/events');
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
// 更新状态为已连接
|
||||
updateStatus();
|
||||
|
||||
// 检查是否有新数据
|
||||
if (data.length !== capturedData.length) {
|
||||
capturedData = data;
|
||||
renderCaptureList();
|
||||
|
||||
// 如果是新数据且当前没有选中,自动选中第一个
|
||||
if (selectedIndex === -1 && data.length > 0) {
|
||||
selectCapture(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function(error) {
|
||||
console.error('SSE error:', error);
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var origin = window.location.origin;
|
||||
document.getElementById('base-url').textContent = origin;
|
||||
document.getElementById('current-url').textContent = window.location.href;
|
||||
|
||||
// 初始加载
|
||||
fetchData();
|
||||
|
||||
// 每秒刷新数据
|
||||
setInterval(fetchData, 1000);
|
||||
// 设置 SSE 连接
|
||||
setupSSE();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -13,35 +13,6 @@ const config = require('./cli.js')
|
||||
metavar: 'address',
|
||||
help: 'specify server host',
|
||||
})
|
||||
.option(['-u', '--proxy-url'], {
|
||||
metavar: 'url',
|
||||
help: 'request through upstream proxy',
|
||||
})
|
||||
.option(['-f', '--force-host'], {
|
||||
metavar: 'host',
|
||||
help: 'force the netease server ip',
|
||||
})
|
||||
.option(['-o', '--match-order'], {
|
||||
metavar: 'source',
|
||||
nargs: '+',
|
||||
help: 'set priority of sources',
|
||||
})
|
||||
.option(['-t', '--token'], {
|
||||
metavar: 'token',
|
||||
help: 'set up proxy authentication',
|
||||
})
|
||||
.option(['-e', '--endpoint'], {
|
||||
metavar: 'url',
|
||||
help: 'replace virtual endpoint with public host',
|
||||
})
|
||||
.option(['-s', '--strict'], {
|
||||
action: 'store_true',
|
||||
help: 'enable proxy limitation',
|
||||
})
|
||||
.option(['-c', '--cnrelay'], {
|
||||
metavar: 'cnrelay',
|
||||
help: 'Mainland China relay to get music url',
|
||||
})
|
||||
.option(['-h', '--help'], { action: 'help' })
|
||||
.parse(process.argv);
|
||||
|
||||
@ -61,52 +32,19 @@ if (config.port.some(invalid)) {
|
||||
if (!process.env.PORT) {
|
||||
process.env.PORT = process.env.PORT || '3000';
|
||||
}
|
||||
if (config.proxyUrl && !/http(s?):\/\/.+:\d+/.test(config.proxyUrl)) {
|
||||
console.log('Please check the proxy url.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!config.endpoint) config.endpoint = 'https://music.163.com';
|
||||
else if (config.endpoint === '-') config.endpoint = '';
|
||||
else if (!/http(s?):\/\/.+/.test(config.endpoint)) {
|
||||
console.log('Please check the endpoint host.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (config.forceHost && require('net').isIP(config.forceHost) === 0) {
|
||||
console.log('Please check the server host.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (config.matchOrder) {
|
||||
const provider = Object.keys(require('./consts').PROVIDERS);
|
||||
const candidate = config.matchOrder;
|
||||
if (candidate.some((key, index) => index != candidate.indexOf(key))) {
|
||||
console.log('Please check the duplication in match order.');
|
||||
process.exit(1);
|
||||
} else if (candidate.some((key) => !provider.includes(key))) {
|
||||
console.log('Please check the availability of match sources.');
|
||||
process.exit(1);
|
||||
}
|
||||
global.source = candidate;
|
||||
}
|
||||
if (config.token && !/\S+:\S+/.test(config.token)) {
|
||||
console.log('Please check the authentication token.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { logScope } = require('./logger');
|
||||
const parse = require('url').parse;
|
||||
const hook = require('./hook');
|
||||
const server = require('./server');
|
||||
const { CacheStorageGroup } = require('./cache');
|
||||
const logger = logScope('app');
|
||||
const random = (array) => array[Math.floor(Math.random() * array.length)];
|
||||
const target = Array.from(hook.target.host);
|
||||
|
||||
global.port = config.port;
|
||||
global.proxy = config.proxyUrl ? parse(config.proxyUrl) : null;
|
||||
global.hosts = target.reduce(
|
||||
(result, host) => Object.assign(result, { [host]: config.forceHost }),
|
||||
{}
|
||||
);
|
||||
global.proxy = null;
|
||||
global.hosts = {};
|
||||
global.endpoint = 'https://music.163.com';
|
||||
|
||||
server.whitelist = [
|
||||
'://[\\w.]*music\\.126\\.net',
|
||||
'://[\\w.]*vod\\.126\\.net',
|
||||
@ -114,14 +52,8 @@ server.whitelist = [
|
||||
'://[\\w.]*\\.netease.com',
|
||||
'://[\\w.]*\\.163yun.com',
|
||||
];
|
||||
global.cnrelay = config.cnrelay;
|
||||
if (config.strict) server.blacklist.push('.*');
|
||||
server.authentication = config.token || null;
|
||||
global.endpoint = config.endpoint;
|
||||
if (config.endpoint) server.whitelist.push(escape(config.endpoint));
|
||||
|
||||
// hosts['music.httpdns.c.163.com'] = random(['59.111.181.35', '59.111.181.38'])
|
||||
// hosts['httpdns.n.netease.com'] = random(['59.111.179.213', '59.111.179.214'])
|
||||
if (config.endpoint) server.whitelist.push(escape(config.endpoint));
|
||||
|
||||
const dns = (host) =>
|
||||
new Promise((resolve, reject) =>
|
||||
@ -131,44 +63,8 @@ const dns = (host) =>
|
||||
: resolve(records.map((record) => record.address))
|
||||
)
|
||||
);
|
||||
const httpdns = (host) =>
|
||||
require('./request')('POST', 'http://music.httpdns.c.163.com/d', {}, host)
|
||||
.then((response) => response.json())
|
||||
.then((jsonBody) =>
|
||||
jsonBody.dns.reduce(
|
||||
(result, domain) => result.concat(domain.ips),
|
||||
[]
|
||||
)
|
||||
);
|
||||
const httpdns2 = (host) =>
|
||||
require('./request')(
|
||||
'GET',
|
||||
'http://httpdns.n.netease.com/httpdns/v2/d?domain=' + host
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((jsonBody) =>
|
||||
Object.keys(jsonBody.data)
|
||||
.map((key) => jsonBody.data[key])
|
||||
.reduce((result, value) => result.concat(value.ip || []), [])
|
||||
);
|
||||
|
||||
// Allow enabling HTTPDNS queries with `ENABLE_HTTPDNS=true`
|
||||
// It seems broken - BETTER TO NOT ENABLE IT!
|
||||
const dnsSource =
|
||||
process.env.ENABLE_HTTPDNS === 'true' ? [httpdns, httpdns2] : [];
|
||||
|
||||
// Start the "Clean Cache" background task.
|
||||
const csgInstance = CacheStorageGroup.getInstance();
|
||||
setInterval(
|
||||
() => {
|
||||
csgInstance.cleanup();
|
||||
},
|
||||
15 * 60 * 1000
|
||||
);
|
||||
|
||||
Promise.all(
|
||||
dnsSource.map((query) => query(target.join(','))).concat(target.map(dns))
|
||||
)
|
||||
Promise.all(target.map(dns))
|
||||
.then((result) => {
|
||||
const { host } = hook.target;
|
||||
result.forEach((array) => array.forEach(host.add, host));
|
||||
@ -189,9 +85,8 @@ Promise.all(
|
||||
server.https
|
||||
.listen(port[1], address)
|
||||
.once('listening', () => log(1));
|
||||
if (cnrelay) logger.info(`CNRelay: ${cnrelay}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
0
src/server/certs.txt
Normal file
0
src/server/certs.txt
Normal file
@ -1,20 +0,0 @@
|
||||
const DEFAULT_SOURCE = ['kugou', 'bodian', 'migu', 'ytdlp'];
|
||||
const PROVIDERS = {
|
||||
qq: require('./provider/qq'),
|
||||
kugou: require('./provider/kugou'),
|
||||
kuwo: require('./provider/kuwo'),
|
||||
bodian: require('./provider/bodian'),
|
||||
migu: require('./provider/migu'),
|
||||
joox: require('./provider/joox'),
|
||||
youtube: require('./provider/youtube'),
|
||||
youtubedl: require('./provider/youtube-dl'),
|
||||
ytdlp: require('./provider/yt-dlp'),
|
||||
bilibili: require('./provider/bilibili'),
|
||||
bilivideo: require('./provider/bilivideo'),
|
||||
pyncmd: require('./provider/pyncmd'),
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_SOURCE,
|
||||
PROVIDERS,
|
||||
};
|
||||
@ -71,67 +71,6 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
},
|
||||
miguapi: {
|
||||
encryptBody: (object) => {
|
||||
const text = JSON.stringify(object);
|
||||
const derive = (password, salt, keyLength, ivSize) => {
|
||||
// EVP_BytesToKey
|
||||
salt = salt || Buffer.alloc(0);
|
||||
const keySize = keyLength / 8;
|
||||
const repeat = Math.ceil((keySize + ivSize * 8) / 32);
|
||||
const buffer = Buffer.concat(
|
||||
Array(repeat)
|
||||
.fill(null)
|
||||
.reduce(
|
||||
(result) =>
|
||||
result.concat(
|
||||
crypto
|
||||
.createHash('md5')
|
||||
.update(
|
||||
Buffer.concat([
|
||||
result.slice(-1)[0],
|
||||
password,
|
||||
salt,
|
||||
])
|
||||
)
|
||||
.digest()
|
||||
),
|
||||
[Buffer.alloc(0)]
|
||||
)
|
||||
);
|
||||
return {
|
||||
key: buffer.slice(0, keySize),
|
||||
iv: buffer.slice(keySize, keySize + ivSize),
|
||||
};
|
||||
};
|
||||
const password = Buffer.from(
|
||||
crypto.randomBytes(32).toString('hex')
|
||||
),
|
||||
salt = crypto.randomBytes(8);
|
||||
const key =
|
||||
'-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8asrfSaoOb4je+DSmKdriQJKWVJ2oDZrs3wi5W67m3LwTB9QVR+cE3XWU21Nx+YBxS0yun8wDcjgQvYt625ZCcgin2ro/eOkNyUOTBIbuj9CvMnhUYiR61lC1f1IGbrSYYimqBVSjpifVufxtx/I3exReZosTByYp4Xwpb1+WAQIDAQAB\n-----END PUBLIC KEY-----';
|
||||
const secret = derive(password, salt, 256, 16);
|
||||
const cipher = crypto.createCipheriv(
|
||||
'aes-256-cbc',
|
||||
secret.key,
|
||||
secret.iv
|
||||
);
|
||||
return bodyify({
|
||||
data: Buffer.concat([
|
||||
Buffer.from('Salted__'),
|
||||
salt,
|
||||
cipher.update(Buffer.from(text)),
|
||||
cipher.final(),
|
||||
]).toString('base64'),
|
||||
secKey: crypto
|
||||
.publicEncrypt(
|
||||
{ key, padding: crypto.constants.RSA_PKCS1_PADDING },
|
||||
password
|
||||
)
|
||||
.toString('base64'),
|
||||
});
|
||||
},
|
||||
},
|
||||
base64: {
|
||||
encode: (text, charset) =>
|
||||
Buffer.from(text, charset)
|
||||
@ -144,27 +83,6 @@ module.exports = {
|
||||
'base64'
|
||||
).toString(charset),
|
||||
},
|
||||
uri: {
|
||||
retrieve: (id) => {
|
||||
id = id.toString().trim();
|
||||
const key = '3go8&$8*3*3h0k(2)2';
|
||||
const string = Array.from(Array(id.length).keys())
|
||||
.map((index) =>
|
||||
String.fromCharCode(
|
||||
id.charCodeAt(index) ^
|
||||
key.charCodeAt(index % key.length)
|
||||
)
|
||||
)
|
||||
.join('');
|
||||
const result = crypto
|
||||
.createHash('md5')
|
||||
.update(string)
|
||||
.digest('base64')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/\+/g, '-');
|
||||
return `http://p1.music.126.net/${result}/${id}`;
|
||||
},
|
||||
},
|
||||
md5: {
|
||||
digest: (value) => crypto.createHash('md5').update(value).digest('hex'),
|
||||
pipe: (source) =>
|
||||
@ -188,8 +106,4 @@ module.exports = {
|
||||
.slice(0, length),
|
||||
uuid: () => crypto.randomUUID(),
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
module.exports.kuwoapi = require('./kwDES');
|
||||
} catch (e) {}
|
||||
};
|
||||
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")
|
||||
@ -35,12 +35,6 @@ hook.target.host = new Set([
|
||||
'apm3.music.163.com',
|
||||
'interface.music.163.com.163jiasu.com',
|
||||
'interface3.music.163.com.163jiasu.com',
|
||||
// 'mam.netease.com',
|
||||
// 'api.iplay.163.com', // look living
|
||||
// 'ac.dun.163yun.com',
|
||||
// 'crash.163.com',
|
||||
// 'clientlog.music.163.com',
|
||||
// 'clientlog3.music.163.com'
|
||||
]);
|
||||
|
||||
hook.target.path = new Set([
|
||||
@ -139,7 +133,6 @@ hook.request.before = (ctx) => {
|
||||
) &&
|
||||
req.method === 'POST' &&
|
||||
(url.path.startsWith('/eapi/') || // eapi
|
||||
// url.path.startsWith('/api/') || // api
|
||||
url.path.startsWith('/api/linux/forward')) // linuxapi
|
||||
) {
|
||||
return request
|
||||
@ -180,12 +173,12 @@ hook.request.before = (ctx) => {
|
||||
),
|
||||
'hex'
|
||||
)
|
||||
)
|
||||
.toString()
|
||||
);
|
||||
netease.path = parse(data.url).path;
|
||||
netease.param = data.params;
|
||||
break;
|
||||
)
|
||||
.toString()
|
||||
);
|
||||
netease.path = parse(data.url).path;
|
||||
netease.param = data.params;
|
||||
break;
|
||||
case 'eapi':
|
||||
data = crypto.eapi
|
||||
.decrypt(
|
||||
@ -196,22 +189,22 @@ hook.request.before = (ctx) => {
|
||||
),
|
||||
'hex'
|
||||
)
|
||||
)
|
||||
.toString()
|
||||
)
|
||||
.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;
|
||||
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)
|
||||
@ -220,9 +213,9 @@ hook.request.before = (ctx) => {
|
||||
let [key, value] = pair.split('=');
|
||||
data[key] = value;
|
||||
});
|
||||
netease.path = url.path;
|
||||
netease.param = data;
|
||||
break;
|
||||
netease.path = url.path;
|
||||
netease.param = data;
|
||||
break;
|
||||
default:
|
||||
// unsupported crypto
|
||||
break;
|
||||
@ -230,30 +223,6 @@ hook.request.before = (ctx) => {
|
||||
netease.path = netease.path.replace(/\/\d*$/, '');
|
||||
ctx.netease = netease;
|
||||
console.log(netease.path, netease.param) // 这里输出了网易云音乐的抓包数据, 重点看这里
|
||||
|
||||
if (netease.path === '/api/song/enhance/download/url')
|
||||
return pretendPlay(ctx);
|
||||
|
||||
if (netease.path === '/api/song/enhance/download/url/v1')
|
||||
return pretendPlayV1(ctx);
|
||||
|
||||
if (BLOCK_ADS) {
|
||||
if (netease.path.startsWith('/api/ad')) {
|
||||
ctx.error = new Error('ADs blocked.');
|
||||
ctx.decision = 'close';
|
||||
}
|
||||
}
|
||||
|
||||
if (DISABLE_UPGRADE_CHECK) {
|
||||
if (
|
||||
netease.path.match(
|
||||
/^\/api(\/v1)?\/(android|ios|osx|pc)\/(upgrade|version)/
|
||||
)
|
||||
) {
|
||||
ctx.error = new Error('Upgrade check blocked.');
|
||||
ctx.decision = 'close';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(
|
||||
@ -287,9 +256,6 @@ hook.request.before = (ctx) => {
|
||||
req.headers['cookie'] = null;
|
||||
ctx.package = { id };
|
||||
ctx.decision = 'proxy';
|
||||
// if (url.href.includes('google'))
|
||||
// return request('GET', req.url, req.headers, null, parse('http://127.0.0.1:1080'))
|
||||
// .then(response => (ctx.res.writeHead(response.statusCode, response.headers), response.pipe(ctx.res)))
|
||||
} catch (error) {
|
||||
ctx.error = error;
|
||||
ctx.decision = 'close';
|
||||
@ -340,170 +306,6 @@ hook.request.after = (ctx) => {
|
||||
};
|
||||
axios.post(`http://localhost:${process.env.PORT || 3000}/api/capture`, dataToSend)
|
||||
.catch(err => logger.error('Failed to send data to frontend:', err));
|
||||
|
||||
|
||||
|
||||
if (
|
||||
netease.path === '/batch' ||
|
||||
netease.path === '/api/batch' ||
|
||||
netease.path === vipPath
|
||||
) {
|
||||
const info =
|
||||
netease.path === vipPath
|
||||
? netease.jsonBody
|
||||
: netease.jsonBody[vipPath];
|
||||
const defaultPackage = {
|
||||
iconUrl: null,
|
||||
dynamicIconUrl: null,
|
||||
isSign: false,
|
||||
isSignIap: false,
|
||||
isSignDeduct: false,
|
||||
isSignIapDeduct: false,
|
||||
};
|
||||
const vipLevel = 7; // ? months
|
||||
if (
|
||||
info &&
|
||||
(LOCAL_VIP_UID.length === 0 ||
|
||||
LOCAL_VIP_UID.includes(info.data.userId))
|
||||
) {
|
||||
try {
|
||||
const nowTime =
|
||||
info.data.now || new Date().getTime();
|
||||
const expireTime = nowTime + 31622400000;
|
||||
info.data.redVipLevel = vipLevel;
|
||||
info.data.redVipAnnualCount = 1;
|
||||
|
||||
info.data.musicPackage = {
|
||||
...defaultPackage,
|
||||
...info.data.musicPackage,
|
||||
vipCode: 230,
|
||||
vipLevel,
|
||||
expireTime,
|
||||
};
|
||||
|
||||
info.data.associator = {
|
||||
...defaultPackage,
|
||||
...info.data.associator,
|
||||
vipCode: 100,
|
||||
vipLevel,
|
||||
expireTime,
|
||||
};
|
||||
|
||||
if (ENABLE_LOCAL_SVIP) {
|
||||
info.data.redplus = {
|
||||
...defaultPackage,
|
||||
...info.data.redplus,
|
||||
vipCode: 300,
|
||||
vipLevel,
|
||||
expireTime,
|
||||
};
|
||||
|
||||
info.data.albumVip = {
|
||||
...defaultPackage,
|
||||
...info.data.albumVip,
|
||||
vipCode: 400,
|
||||
vipLevel: 0,
|
||||
expireTime,
|
||||
};
|
||||
}
|
||||
|
||||
if (netease.path === vipPath)
|
||||
netease.jsonBody = info;
|
||||
else netease.jsonBody[vipPath] = info;
|
||||
} catch (error) {
|
||||
logger.debug(
|
||||
{ err: error },
|
||||
'Unable to apply the local VIP.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
new Set([401, 512]).has(netease.jsonBody.code) &&
|
||||
!netease.web
|
||||
) {
|
||||
if (netease.path.includes('/usertool/sound/'))
|
||||
return unblockSoundEffects(netease.jsonBody);
|
||||
else if (netease.path.includes('batch')) {
|
||||
for (const key in netease.jsonBody) {
|
||||
if (key.includes('/usertool/sound/'))
|
||||
unblockSoundEffects(netease.jsonBody[key]);
|
||||
}
|
||||
} else if (netease.path.includes('/vipauth/app/auth/query'))
|
||||
return unblockLyricsEffects(netease.jsonBody);
|
||||
|
||||
.then(() => {
|
||||
['transfer-encoding', 'content-encoding', 'content-length']
|
||||
.filter((key) => key in proxyRes.headers)
|
||||
.forEach((key) => delete proxyRes.headers[key]);
|
||||
|
||||
const inject = (key, value) => {
|
||||
if (typeof value === 'object' && value != null) {
|
||||
if ('cp' in value) value['cp'] = 1;
|
||||
if ('fee' in value) value['fee'] = 0;
|
||||
if (
|
||||
'downloadMaxbr' in value &&
|
||||
value['downloadMaxbr'] === 0
|
||||
)
|
||||
value['downloadMaxbr'] = 320000;
|
||||
if (
|
||||
'dl' in value &&
|
||||
'downloadMaxbr' in value &&
|
||||
value['dl'] < value['downloadMaxbr']
|
||||
)
|
||||
value['dl'] = value['downloadMaxbr'];
|
||||
if ('playMaxbr' in value && value['playMaxbr'] === 0)
|
||||
value['playMaxbr'] = 320000;
|
||||
if (
|
||||
'pl' in value &&
|
||||
'playMaxbr' in value &&
|
||||
value['pl'] < value['playMaxbr']
|
||||
)
|
||||
value['pl'] = value['playMaxbr'];
|
||||
if ('sp' in value && 'st' in value && 'subp' in value) {
|
||||
// batch modify
|
||||
value['sp'] = 7;
|
||||
value['st'] = 0;
|
||||
value['subp'] = 1;
|
||||
}
|
||||
if (
|
||||
'start' in value &&
|
||||
'end' in value &&
|
||||
'playable' in value &&
|
||||
'unplayableType' in value &&
|
||||
'unplayableUserIds' in value
|
||||
) {
|
||||
value['start'] = 0;
|
||||
value['end'] = 0;
|
||||
value['playable'] = true;
|
||||
value['unplayableType'] = 'unknown';
|
||||
value['unplayableUserIds'] = [];
|
||||
}
|
||||
if ('noCopyrightRcmd' in value)
|
||||
value['noCopyrightRcmd'] = null;
|
||||
if ('payed' in value && value['payed'] == 0)
|
||||
value['payed'] = 1;
|
||||
if ('flLevel' in value && value['flLevel'] === 'none')
|
||||
value['flLevel'] = 'exhigh';
|
||||
if ('plLevel' in value && value['plLevel'] === 'none')
|
||||
value['plLevel'] = 'exhigh';
|
||||
if ('dlLevel' in value && value['dlLevel'] === 'none')
|
||||
value['dlLevel'] = 'exhigh';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
let body = JSON.stringify(netease.jsonBody, inject);
|
||||
body = body.replace(
|
||||
/([^\\]"\s*:\s*)"(\d{16,})L"(\s*[}|,])/g,
|
||||
'$1$2$3'
|
||||
); // for js precision
|
||||
proxyRes.body = netease.e_r // eapi's e_r is true, needs to be encrypted
|
||||
? crypto.eapi.encrypt(Buffer.from(body))
|
||||
: body;
|
||||
})
|
||||
.catch(
|
||||
(error) =>
|
||||
@ -557,89 +359,4 @@ hook.negotiate.before = (ctx) => {
|
||||
}
|
||||
};
|
||||
|
||||
const pretendPlay = (ctx) => {
|
||||
const { req, netease } = ctx;
|
||||
const turn = 'http://music.163.com/api/song/enhance/player/url';
|
||||
let query;
|
||||
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;
|
||||
};
|
||||
|
||||
const pretendPlayV1 = (ctx) => {
|
||||
const { req, netease } = ctx;
|
||||
const turn = 'http://music.163.com/api/song/enhance/player/url/v1';
|
||||
let query;
|
||||
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;
|
||||
};
|
||||
|
||||
const unblockSoundEffects = (obj) => {
|
||||
logger.debug('unblockSoundEffects() has been triggered.');
|
||||
const { data, code } = obj;
|
||||
if (code === 200) {
|
||||
if (Array.isArray(data))
|
||||
data.map((item) => {
|
||||
if (item.type) item.type = 1;
|
||||
});
|
||||
else if (data.type) data.type = 1;
|
||||
}
|
||||
};
|
||||
|
||||
const unblockLyricsEffects = (obj) => {
|
||||
logger.debug('unblockLyricsEffects() has been triggered.');
|
||||
const { data, code } = obj;
|
||||
if (code === 200 && Array.isArray(data)) {
|
||||
data.forEach((item) => {
|
||||
if ('canUse' in item) item.canUse = true;
|
||||
if ('canNotUseReasonCode' in item) item.canNotUseReasonCode = 200;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = hook;
|
||||
module.exports = hook;
|
||||
@ -1,585 +0,0 @@
|
||||
/*
|
||||
Thanks to
|
||||
https://github.com/XuShaohua/kwplayer/blob/master/kuwo/DES.py
|
||||
https://github.com/Levi233/MusicPlayer/blob/master/app/src/main/java/com/chenhao/musicplayer/utils/crypt/KuwoDES.java
|
||||
*/
|
||||
|
||||
const Long = (n) => {
|
||||
const bN = BigInt(n);
|
||||
|
||||
return {
|
||||
low: Number(bN),
|
||||
valueOf: () => bN.valueOf(),
|
||||
toString: () => bN.toString(),
|
||||
not: () => Long(~bN),
|
||||
isNegative: () => bN < 0,
|
||||
or: (x) => Long(bN | BigInt(x)),
|
||||
and: (x) => Long(bN & BigInt(x)),
|
||||
xor: (x) => Long(bN ^ BigInt(x)),
|
||||
equals: (x) => bN === BigInt(x),
|
||||
multiply: (x) => Long(bN * BigInt(x)),
|
||||
shiftLeft: (x) => Long(bN << BigInt(x)),
|
||||
shiftRight: (x) => Long(bN >> BigInt(x)),
|
||||
};
|
||||
};
|
||||
|
||||
const range = (n) => Array.from(new Array(n).keys());
|
||||
const power = (base, index) =>
|
||||
Array(index)
|
||||
.fill(null)
|
||||
.reduce((result) => result.multiply(base), Long(1));
|
||||
const LongArray = (...array) =>
|
||||
array.map((n) => (n === -1 ? Long(-1, -1) : Long(n)));
|
||||
|
||||
// EXPANSION
|
||||
const arrayE = LongArray(
|
||||
31,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
-1,
|
||||
-1,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
-1,
|
||||
-1,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
-1,
|
||||
-1,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
-1,
|
||||
-1,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
-1,
|
||||
-1,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
-1,
|
||||
-1,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
-1,
|
||||
-1,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
-1,
|
||||
-1
|
||||
);
|
||||
|
||||
// INITIAL_PERMUTATION
|
||||
const arrayIP = LongArray(
|
||||
57,
|
||||
49,
|
||||
41,
|
||||
33,
|
||||
25,
|
||||
17,
|
||||
9,
|
||||
1,
|
||||
59,
|
||||
51,
|
||||
43,
|
||||
35,
|
||||
27,
|
||||
19,
|
||||
11,
|
||||
3,
|
||||
61,
|
||||
53,
|
||||
45,
|
||||
37,
|
||||
29,
|
||||
21,
|
||||
13,
|
||||
5,
|
||||
63,
|
||||
55,
|
||||
47,
|
||||
39,
|
||||
31,
|
||||
23,
|
||||
15,
|
||||
7,
|
||||
56,
|
||||
48,
|
||||
40,
|
||||
32,
|
||||
24,
|
||||
16,
|
||||
8,
|
||||
0,
|
||||
58,
|
||||
50,
|
||||
42,
|
||||
34,
|
||||
26,
|
||||
18,
|
||||
10,
|
||||
2,
|
||||
60,
|
||||
52,
|
||||
44,
|
||||
36,
|
||||
28,
|
||||
20,
|
||||
12,
|
||||
4,
|
||||
62,
|
||||
54,
|
||||
46,
|
||||
38,
|
||||
30,
|
||||
22,
|
||||
14,
|
||||
6
|
||||
);
|
||||
|
||||
// INVERSE_PERMUTATION
|
||||
const arrayIP_1 = LongArray(
|
||||
39,
|
||||
7,
|
||||
47,
|
||||
15,
|
||||
55,
|
||||
23,
|
||||
63,
|
||||
31,
|
||||
38,
|
||||
6,
|
||||
46,
|
||||
14,
|
||||
54,
|
||||
22,
|
||||
62,
|
||||
30,
|
||||
37,
|
||||
5,
|
||||
45,
|
||||
13,
|
||||
53,
|
||||
21,
|
||||
61,
|
||||
29,
|
||||
36,
|
||||
4,
|
||||
44,
|
||||
12,
|
||||
52,
|
||||
20,
|
||||
60,
|
||||
28,
|
||||
35,
|
||||
3,
|
||||
43,
|
||||
11,
|
||||
51,
|
||||
19,
|
||||
59,
|
||||
27,
|
||||
34,
|
||||
2,
|
||||
42,
|
||||
10,
|
||||
50,
|
||||
18,
|
||||
58,
|
||||
26,
|
||||
33,
|
||||
1,
|
||||
41,
|
||||
9,
|
||||
49,
|
||||
17,
|
||||
57,
|
||||
25,
|
||||
32,
|
||||
0,
|
||||
40,
|
||||
8,
|
||||
48,
|
||||
16,
|
||||
56,
|
||||
24
|
||||
);
|
||||
|
||||
// ROTATES
|
||||
const arrayLs = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
|
||||
const arrayLsMask = LongArray(0, 0x100001, 0x300003);
|
||||
const arrayMask = range(64).map((n) => power(2, n));
|
||||
arrayMask[arrayMask.length - 1] = arrayMask[arrayMask.length - 1].multiply(-1);
|
||||
|
||||
// PERMUTATION
|
||||
const arrayP = LongArray(
|
||||
15,
|
||||
6,
|
||||
19,
|
||||
20,
|
||||
28,
|
||||
11,
|
||||
27,
|
||||
16,
|
||||
0,
|
||||
14,
|
||||
22,
|
||||
25,
|
||||
4,
|
||||
17,
|
||||
30,
|
||||
9,
|
||||
1,
|
||||
7,
|
||||
23,
|
||||
13,
|
||||
31,
|
||||
26,
|
||||
2,
|
||||
8,
|
||||
18,
|
||||
12,
|
||||
29,
|
||||
5,
|
||||
21,
|
||||
10,
|
||||
3,
|
||||
24
|
||||
);
|
||||
|
||||
// PERMUTED_CHOICE1
|
||||
const arrayPC_1 = LongArray(
|
||||
56,
|
||||
48,
|
||||
40,
|
||||
32,
|
||||
24,
|
||||
16,
|
||||
8,
|
||||
0,
|
||||
57,
|
||||
49,
|
||||
41,
|
||||
33,
|
||||
25,
|
||||
17,
|
||||
9,
|
||||
1,
|
||||
58,
|
||||
50,
|
||||
42,
|
||||
34,
|
||||
26,
|
||||
18,
|
||||
10,
|
||||
2,
|
||||
59,
|
||||
51,
|
||||
43,
|
||||
35,
|
||||
62,
|
||||
54,
|
||||
46,
|
||||
38,
|
||||
30,
|
||||
22,
|
||||
14,
|
||||
6,
|
||||
61,
|
||||
53,
|
||||
45,
|
||||
37,
|
||||
29,
|
||||
21,
|
||||
13,
|
||||
5,
|
||||
60,
|
||||
52,
|
||||
44,
|
||||
36,
|
||||
28,
|
||||
20,
|
||||
12,
|
||||
4,
|
||||
27,
|
||||
19,
|
||||
11,
|
||||
3
|
||||
);
|
||||
|
||||
// PERMUTED_CHOICE2
|
||||
const arrayPC_2 = LongArray(
|
||||
13,
|
||||
16,
|
||||
10,
|
||||
23,
|
||||
0,
|
||||
4,
|
||||
-1,
|
||||
-1,
|
||||
2,
|
||||
27,
|
||||
14,
|
||||
5,
|
||||
20,
|
||||
9,
|
||||
-1,
|
||||
-1,
|
||||
22,
|
||||
18,
|
||||
11,
|
||||
3,
|
||||
25,
|
||||
7,
|
||||
-1,
|
||||
-1,
|
||||
15,
|
||||
6,
|
||||
26,
|
||||
19,
|
||||
12,
|
||||
1,
|
||||
-1,
|
||||
-1,
|
||||
40,
|
||||
51,
|
||||
30,
|
||||
36,
|
||||
46,
|
||||
54,
|
||||
-1,
|
||||
-1,
|
||||
29,
|
||||
39,
|
||||
50,
|
||||
44,
|
||||
32,
|
||||
47,
|
||||
-1,
|
||||
-1,
|
||||
43,
|
||||
48,
|
||||
38,
|
||||
55,
|
||||
33,
|
||||
52,
|
||||
-1,
|
||||
-1,
|
||||
45,
|
||||
41,
|
||||
49,
|
||||
35,
|
||||
28,
|
||||
31,
|
||||
-1,
|
||||
-1
|
||||
);
|
||||
|
||||
const matrixNSBox = [
|
||||
[
|
||||
14, 4, 3, 15, 2, 13, 5, 3, 13, 14, 6, 9, 11, 2, 0, 5, 4, 1, 10, 12, 15,
|
||||
6, 9, 10, 1, 8, 12, 7, 8, 11, 7, 0, 0, 15, 10, 5, 14, 4, 9, 10, 7, 8,
|
||||
12, 3, 13, 1, 3, 6, 15, 12, 6, 11, 2, 9, 5, 0, 4, 2, 11, 14, 1, 7, 8,
|
||||
13,
|
||||
],
|
||||
[
|
||||
15, 0, 9, 5, 6, 10, 12, 9, 8, 7, 2, 12, 3, 13, 5, 2, 1, 14, 7, 8, 11, 4,
|
||||
0, 3, 14, 11, 13, 6, 4, 1, 10, 15, 3, 13, 12, 11, 15, 3, 6, 0, 4, 10, 1,
|
||||
7, 8, 4, 11, 14, 13, 8, 0, 6, 2, 15, 9, 5, 7, 1, 10, 12, 14, 2, 5, 9,
|
||||
],
|
||||
[
|
||||
10, 13, 1, 11, 6, 8, 11, 5, 9, 4, 12, 2, 15, 3, 2, 14, 0, 6, 13, 1, 3,
|
||||
15, 4, 10, 14, 9, 7, 12, 5, 0, 8, 7, 13, 1, 2, 4, 3, 6, 12, 11, 0, 13,
|
||||
5, 14, 6, 8, 15, 2, 7, 10, 8, 15, 4, 9, 11, 5, 9, 0, 14, 3, 10, 7, 1,
|
||||
12,
|
||||
],
|
||||
[
|
||||
7, 10, 1, 15, 0, 12, 11, 5, 14, 9, 8, 3, 9, 7, 4, 8, 13, 6, 2, 1, 6, 11,
|
||||
12, 2, 3, 0, 5, 14, 10, 13, 15, 4, 13, 3, 4, 9, 6, 10, 1, 12, 11, 0, 2,
|
||||
5, 0, 13, 14, 2, 8, 15, 7, 4, 15, 1, 10, 7, 5, 6, 12, 11, 3, 8, 9, 14,
|
||||
],
|
||||
[
|
||||
2, 4, 8, 15, 7, 10, 13, 6, 4, 1, 3, 12, 11, 7, 14, 0, 12, 2, 5, 9, 10,
|
||||
13, 0, 3, 1, 11, 15, 5, 6, 8, 9, 14, 14, 11, 5, 6, 4, 1, 3, 10, 2, 12,
|
||||
15, 0, 13, 2, 8, 5, 11, 8, 0, 15, 7, 14, 9, 4, 12, 7, 10, 9, 1, 13, 6,
|
||||
3,
|
||||
],
|
||||
[
|
||||
12, 9, 0, 7, 9, 2, 14, 1, 10, 15, 3, 4, 6, 12, 5, 11, 1, 14, 13, 0, 2,
|
||||
8, 7, 13, 15, 5, 4, 10, 8, 3, 11, 6, 10, 4, 6, 11, 7, 9, 0, 6, 4, 2, 13,
|
||||
1, 9, 15, 3, 8, 15, 3, 1, 14, 12, 5, 11, 0, 2, 12, 14, 7, 5, 10, 8, 13,
|
||||
],
|
||||
[
|
||||
4, 1, 3, 10, 15, 12, 5, 0, 2, 11, 9, 6, 8, 7, 6, 9, 11, 4, 12, 15, 0, 3,
|
||||
10, 5, 14, 13, 7, 8, 13, 14, 1, 2, 13, 6, 14, 9, 4, 1, 2, 14, 11, 13, 5,
|
||||
0, 1, 10, 8, 3, 0, 11, 3, 5, 9, 4, 15, 2, 7, 8, 12, 15, 10, 7, 6, 12,
|
||||
],
|
||||
[
|
||||
13, 7, 10, 0, 6, 9, 5, 15, 8, 4, 3, 10, 11, 14, 12, 5, 2, 11, 9, 6, 15,
|
||||
12, 0, 3, 4, 1, 14, 13, 1, 2, 7, 8, 1, 2, 12, 15, 10, 4, 0, 3, 13, 14,
|
||||
6, 9, 7, 8, 9, 6, 15, 1, 5, 12, 3, 10, 14, 5, 8, 7, 11, 0, 4, 13, 2, 11,
|
||||
],
|
||||
];
|
||||
|
||||
const bitTransform = (arrInt, n, l) => {
|
||||
// int[], int, long : long
|
||||
let l2 = Long(0);
|
||||
range(n).forEach((i) => {
|
||||
if (arrInt[i].isNegative() || l.and(arrayMask[arrInt[i].low]).equals(0))
|
||||
return;
|
||||
l2 = l2.or(arrayMask[i]);
|
||||
});
|
||||
return l2;
|
||||
};
|
||||
|
||||
const DES64 = (longs, l) => {
|
||||
const pR = range(8).map(() => Long(0));
|
||||
const pSource = [Long(0), Long(0)];
|
||||
let L = Long(0);
|
||||
let R = Long(0);
|
||||
let out = bitTransform(arrayIP, 64, l);
|
||||
pSource[0] = out.and(0xffffffff);
|
||||
pSource[1] = out.and(-4294967296).shiftRight(32);
|
||||
|
||||
range(16).forEach((i) => {
|
||||
let SOut = Long(0);
|
||||
|
||||
R = Long(pSource[1]);
|
||||
R = bitTransform(arrayE, 64, R);
|
||||
R = R.xor(longs[i]);
|
||||
range(8).forEach((j) => {
|
||||
pR[j] = R.shiftRight(j * 8).and(255);
|
||||
});
|
||||
range(8)
|
||||
.reverse()
|
||||
.forEach((sbi) => {
|
||||
SOut = SOut.shiftLeft(4).or(matrixNSBox[sbi][pR[sbi]]);
|
||||
});
|
||||
R = bitTransform(arrayP, 32, SOut);
|
||||
L = Long(pSource[0]);
|
||||
pSource[0] = Long(pSource[1]);
|
||||
pSource[1] = L.xor(R);
|
||||
});
|
||||
pSource.reverse();
|
||||
out = pSource[1]
|
||||
.shiftLeft(32)
|
||||
.and(-4294967296)
|
||||
.or(pSource[0].and(0xffffffff));
|
||||
out = bitTransform(arrayIP_1, 64, out);
|
||||
return out;
|
||||
};
|
||||
|
||||
const subKeys = (l, longs, n) => {
|
||||
// long, long[], int
|
||||
let l2 = bitTransform(arrayPC_1, 56, l);
|
||||
range(16).forEach((i) => {
|
||||
l2 = l2
|
||||
.and(arrayLsMask[arrayLs[i]])
|
||||
.shiftLeft(28 - arrayLs[i])
|
||||
.or(l2.and(arrayLsMask[arrayLs[i]].not()).shiftRight(arrayLs[i]));
|
||||
longs[i] = bitTransform(arrayPC_2, 64, l2);
|
||||
});
|
||||
if (n === 1) {
|
||||
range(8).forEach((j) => {
|
||||
[longs[j], longs[15 - j]] = [longs[15 - j], longs[j]];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const crypt = (msg, key, mode) => {
|
||||
// 处理密钥块
|
||||
let l = Long(0);
|
||||
range(8).forEach((i) => {
|
||||
l = Long(key[i])
|
||||
.shiftLeft(i * 8)
|
||||
.or(l);
|
||||
});
|
||||
|
||||
const j = Math.floor(msg.length / 8);
|
||||
// arrLong1 存放的是转换后的密钥块, 在解密时只需要把这个密钥块反转就行了
|
||||
|
||||
const arrLong1 = range(16).map(() => Long(0));
|
||||
subKeys(l, arrLong1, mode);
|
||||
|
||||
// arrLong2 存放的是前部分的明文
|
||||
const arrLong2 = range(j).map(() => Long(0));
|
||||
|
||||
range(j).forEach((m) => {
|
||||
range(8).forEach((n) => {
|
||||
arrLong2[m] = Long(msg[n + m * 8])
|
||||
.shiftLeft(n * 8)
|
||||
.or(arrLong2[m]);
|
||||
});
|
||||
});
|
||||
|
||||
// 用于存放密文
|
||||
const arrLong3 = range(Math.floor((1 + 8 * (j + 1)) / 8)).map(() =>
|
||||
Long(0)
|
||||
);
|
||||
|
||||
// 计算前部的数据块(除了最后一部分)
|
||||
range(j).forEach((i1) => {
|
||||
arrLong3[i1] = DES64(arrLong1, arrLong2[i1]);
|
||||
});
|
||||
|
||||
// 保存多出来的字节
|
||||
const arrByte1 = msg.slice(j * 8);
|
||||
let l2 = Long(0);
|
||||
|
||||
range(msg.length % 8).forEach((i1) => {
|
||||
l2 = Long(arrByte1[i1])
|
||||
.shiftLeft(i1 * 8)
|
||||
.or(l2);
|
||||
});
|
||||
|
||||
// 计算多出的那一位(最后一位)
|
||||
if (arrByte1.length || mode === 0) arrLong3[j] = DES64(arrLong1, l2); // 解密不需要
|
||||
|
||||
// 将密文转为字节型
|
||||
const arrByte2 = range(8 * arrLong3.length).map(() => 0);
|
||||
let i4 = 0;
|
||||
arrLong3.forEach((l3) => {
|
||||
range(8).forEach((i6) => {
|
||||
arrByte2[i4] = l3.shiftRight(i6 * 8).and(255).low;
|
||||
i4 += 1;
|
||||
});
|
||||
});
|
||||
return Buffer.from(arrByte2);
|
||||
};
|
||||
|
||||
const SECRET_KEY = Buffer.from('ylzsxkwm');
|
||||
const encrypt = (msg) => crypt(msg, SECRET_KEY, 0);
|
||||
const decrypt = (msg) => crypt(msg, SECRET_KEY, 1);
|
||||
const encryptQuery = (query) => encrypt(Buffer.from(query)).toString('base64');
|
||||
|
||||
module.exports = { encrypt, decrypt, encryptQuery };
|
||||
@ -1,8 +1,6 @@
|
||||
const zlib = require('zlib');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const ON_CANCEL = require('./cancel');
|
||||
const RequestCancelled = require('./exceptions/RequestCancelled');
|
||||
const { logScope } = require('./logger');
|
||||
const parse = require('url').parse;
|
||||
const format = require('url').format;
|
||||
@ -62,7 +60,7 @@ const configure = (method, url, headers, proxy) => {
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{url: string, body: RequestExtensionBody, json: () => Promise<T>, jsonp: () => Promise<T>}} RequestExtension
|
||||
* @typedef {{url: string, body: RequestExtensionBody, json: () => Promise<T>}} RequestExtension
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -72,7 +70,6 @@ const configure = (method, url, headers, proxy) => {
|
||||
* @param {Object?} receivedHeaders
|
||||
* @param {unknown?} body
|
||||
* @param {unknown?} proxy
|
||||
* @param {CancelRequest?} cancelRequest
|
||||
* @return {Promise<http.IncomingMessage & RequestExtension<T>>}
|
||||
*/
|
||||
const request = (
|
||||
@ -80,8 +77,7 @@ const request = (
|
||||
receivedUrl,
|
||||
receivedHeaders,
|
||||
body,
|
||||
proxy,
|
||||
cancelRequest
|
||||
proxy
|
||||
) => {
|
||||
const url = parse(receivedUrl);
|
||||
/* @type {Partial<Record<string,string>>} */
|
||||
@ -105,14 +101,6 @@ const request = (
|
||||
logger.debug(`Start requesting ${receivedUrl}`);
|
||||
|
||||
const clientRequest = create(url, proxy)(options);
|
||||
const destroyClientRequest = function () {
|
||||
// We destroy the request and throw RequestCancelled
|
||||
// when the request has been cancelled.
|
||||
clientRequest.destroy(new RequestCancelled(format(url)));
|
||||
};
|
||||
|
||||
cancelRequest?.on(ON_CANCEL, destroyClientRequest);
|
||||
if (cancelRequest?.cancelled ?? false) destroyClientRequest();
|
||||
|
||||
clientRequest
|
||||
.setTimeout(timeoutThreshold, () => {
|
||||
@ -122,7 +110,7 @@ const request = (
|
||||
},
|
||||
`The request timed out, or the requester didn't handle the response.`
|
||||
);
|
||||
destroyClientRequest();
|
||||
clientRequest.destroy();
|
||||
})
|
||||
.on('response', (response) => resolve(response))
|
||||
.on('connect', (_, socket) => {
|
||||
@ -146,9 +134,6 @@ const request = (
|
||||
}).then(
|
||||
/** @param {http.IncomingMessage} response */
|
||||
(response) => {
|
||||
if (cancelRequest?.cancelled ?? false)
|
||||
return Promise.reject(new RequestCancelled(format(url)));
|
||||
|
||||
if ([201, 301, 302, 303, 307, 308].includes(response.statusCode)) {
|
||||
const redirectTo = url.resolve(
|
||||
response.headers.location || url.href
|
||||
@ -163,7 +148,6 @@ const request = (
|
||||
url,
|
||||
body: (raw) => read(response, raw),
|
||||
json: () => json(response),
|
||||
jsonp: () => jsonp(response),
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -192,14 +176,10 @@ const read = (connect, raw) =>
|
||||
});
|
||||
|
||||
const json = (connect) => read(connect, false).then((body) => JSON.parse(body));
|
||||
const jsonp = (connect) =>
|
||||
read(connect, false).then((body) =>
|
||||
JSON.parse(body.slice(body.indexOf('(') + 1, -')'.length))
|
||||
);
|
||||
|
||||
request.read = read;
|
||||
request.create = create;
|
||||
request.translate = translate;
|
||||
request.configure = configure;
|
||||
|
||||
module.exports = request;
|
||||
module.exports = request;
|
||||
@ -1,16 +1,16 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIJAOqZ7l8q9YAMMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
|
||||
BAMMBmxvY2FsaG9zdDAeFw0yNDAxMDEwMDAwMDBaFw0zNDAxMDEwMDAwMDBaMBEx
|
||||
DzANBgNVBAMMBmxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
||||
v5KXq8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R
|
||||
5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8
|
||||
R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq
|
||||
8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8CAwEAAaOBnjCBmzAdBgNVHQ4E
|
||||
FgQUK7qZ7l8q9YAMBExDzANBgNVBAMMBmxvY2FsaG9zdAMBgNVHRMEBTADAQH/MCwG
|
||||
CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
|
||||
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEAv5KX
|
||||
q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X
|
||||
5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9
|
||||
X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L
|
||||
9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R5L9X5q8Y3Zq8R
|
||||
MIICgjCCAgigAwIBAgIUafzDKFqhg8XS5YogLkI9oGsngkUwCgYIKoZIzj0EAwMw
|
||||
RDELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRlYXNlTXVzaWMgUm9v
|
||||
dCBDQTEPMA0GA1UECgwGbm9ib2R5MB4XDTI1MDcyNDA4NDY1NloXDTI2MDcyNDA4
|
||||
NDY1NlowezELMAkGA1UEBhMCQ04xETAPBgNVBAcMCEhhbmd6aG91MSwwKgYDVQQK
|
||||
DCNOZXRFYXNlIChIYW5nemhvdSkgTmV0d29yayBDby4sIEx0ZDERMA8GA1UECwwI
|
||||
SVQgRGVwdC4xGDAWBgNVBAMMDyoubXVzaWMuMTYzLmNvbTB2MBAGByqGSM49AgEG
|
||||
BSuBBAAiA2IABACkvqL3b68Pv11nQVwm/9BNWGuh1fBKlqYZqnyr9Gy1xEQtf3we
|
||||
WlwgVmX9rqu9nNEiAbbxIt9aGliHurKkE22+FiOKC4vduYlyyIXKlCAFXigHKr/k
|
||||
6uKgc2jPfeYoC6OBgzCBgDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAg
|
||||
gg1tdXNpYy4xNjMuY29tgg8qLm11c2ljLjE2My5jb20wHQYDVR0OBBYEFEeHCj4z
|
||||
PzDre8No6wsWbrpmQ8hPMB8GA1UdIwQYMBaAFGW+hCo4x24AIMIYbUjR4NRlnzx1
|
||||
MAoGCCqGSM49BAMDA2gAMGUCMQDD0cOUV43GSLH8bbbkw4KD6kVoknm6z6sjE5vk
|
||||
ZNmgjqduSn6XzCNiiwwhRrsmtVkCMCX8uRVIIG20mdcK2YSwlSgw5mHTJsNFRnfn
|
||||
x8ft2l8Tk2yZcqFSsvuVKvBwZh7bWA==
|
||||
-----END CERTIFICATE-----
|
||||
@ -1,28 +1,9 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDK3UbsHMKuVo1c
|
||||
Ye/54XWH7SZM+2hDAKqrCcE3lo96CVazxtUHcd7hHFK/oVHC/9IgntOr0hunp0NH
|
||||
jCUjor9ZAcDECQEmjU9CIO+TsuVKD8EaqJPArO3dKRvALGhE9Abs8MqphlRxgCxO
|
||||
4BWaEffWSjl8+owYH/VZtTG+onQlY6uC2B50YnzJAtgv9nDZwfolxzYdK0xt/SaU
|
||||
JQbQxc4tQ5G/+roqpHSLTAqL7ts3V0GY8ygWzal9khbkNAR3E1zG+fESzysyOlDu
|
||||
oqd7FExnyAd6TSKDcmiQYgDKSEcZwunwNAvNi4UVG9XaPLzRq+KAe99R6KvjVf6u
|
||||
1IfTTWeVAgMBAAECggEABtqFYzXUM7hK2+AI82BYs5uvlFmP0S88P4SYEmez4lHt
|
||||
VoFOKyfjJBnP3PsTsbx/DBdOBr7OkCePeuL9w/+qXGMdVGzR5ejBXtnKOrmIMAGV
|
||||
XKkXyT7ALLnWQoIoOzXqA8MIJcR+lvTXQ0NdQiJGYZHDRzkXSJDsuhRd0xdIZQbr
|
||||
tI8H9WMH+2BZn4t+5PTlj6EbihBZiqCJ986Vu5z2enPS61PIhnDMF7yxG01G0gNk
|
||||
QrZSFJWd0zPHdbhxrLXe6slFGwDBXE+ad9Shjjq/xaMrSzWaiEQImvTh8d3eLsJK
|
||||
QjJL5z2bWm7EYb1+LQtiX4ioMjav22SavggVQKc11QKBgQDthprmGs+IVveZVLql
|
||||
e3gESsS3+P72fLSF1F4kxMM6wWWypJo5/LYzveskDKK5sXTRR/lBGglpRVuZvnnm
|
||||
UuOrixpfKM8W4nfnrZURbau/0Lfeull7LSJPuNkr4xEA5/zt/cSlg1L/IOPDgTtT
|
||||
4XFd41kZYsAgK2jsXih7bpiZCwKBgQDapIZ9iJG0Yd6uAclD1Swo83UHaoxmJSZw
|
||||
EiAyR07WWBycfb3P18FHW0wejIloyiq8LZR8upa5u8xFcyC3/Z8AlxpojdcHsDE9
|
||||
uuT1Sibc483tto9I+p+YfWIBxJBWA3hMB5gS+t7ssueEdZdrfz1Y8aBYHwA5XFNk
|
||||
7JbbUrSl3wKBgAgCfAK6cLkmRZ80Dj86VKfAZbXWfbKOLgA9UxdmUzcOAoHtrw25
|
||||
ieNgyicjDfG5HDladftOB3c3UYlztOShcu/79t2yoJki9ewoHFjEHACR50Fpg072
|
||||
DKwnjZs/QvmG2S6lWhZCwW+9CjEzkG6ZsZr66axDejsbe6RM4IyZBChVAoGAdId1
|
||||
iphsF7iFxzX6f+WwqI7BE9fMxnAMYXS3pjRtJz5E2X8G2CyEvbRCCJIcdjYxuqOM
|
||||
XUHRLWKTB3zJtmY9BUKDd7AJJ/bW97CRcM45kkbzrTs8eMfioZJJ1uldiApHZjYx
|
||||
7gO5JmxfijBmKIvjNXFqZSz4oJm9dK/H41LcJv8CgYAPMpmgNuPSBq9+Ol/5Vf2v
|
||||
8s4j87SJoOsPEepnlyobnxARnjTuS6BnEuaQHMyvXIMzXLzy0kA3jGFd9j2k31Fw
|
||||
4JOTHVwHrgJboEp/F3MssHw+SYUKgHWqqVPlwME1Zgb5kgZpxeLIRkS69nPCEjD7
|
||||
hmOSlIN+qgaqE5jIiK76RQ==
|
||||
-----END PRIVATE KEY-----
|
||||
-----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-----
|
||||
Loading…
x
Reference in New Issue
Block a user