feat: 支持清空抓包信息

This commit is contained in:
ElyPrism 2026-03-13 23:03:34 +08:00
parent 3dea10d82f
commit 1e1111c112
No known key found for this signature in database
4 changed files with 136 additions and 140 deletions

View File

@ -1,125 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="docs/netease.png">
<title>网易云音乐 API Enhanced</title>
<style>
:root {
--fg: #333;
--muted: #666;
--border: #ddd;
--bg: #f5f5f5;
--panel: #ffffff;
--accent: #333;
}
* { 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; }
}
header.site-header { margin-bottom: 24px; }
header.site-header h1 { font-size: 28px; font-weight: 600; margin: 0; }
.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; }
}
ul.links { list-style: none; padding: 0; margin: 0; }
ul.links li { margin: 8px 0; }
ul.links a { color: var(--fg); text-decoration: none; border-bottom: 1px dotted var(--border); transition: all 0.2s ease; }
ul.links a:hover { color: var(--accent); border-bottom-color: var(--accent); }
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; }
code { font-family: 'Courier New', monospace; font-size: 13px; }
@media (max-width: 480px) {
code { font-size: 12px; }
}
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>网易云音乐 API Enhanced <span id="api-version" class="badge"></span></h1>
<p class="sub">🔍 A revival project for NeteaseCloudMusicApi Node.js Api Services || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护</p>
</header>
<section class="block">
<h2>状态</h2>
<div class="kvs">
<div>Base URL</div><div id="base-url"></div>
<div>当前页</div><div id="current-url"></div>
</div>
</section>
<section class="block">
<h2>文档</h2>
<p><a href="/docs" target="_blank">查看在线文档</a></p>
</section>
<section class="block">
<h2>常用接口</h2>
<ul class="links">
<li><a href="/search?keywords=妖精小姐的魔法邀约">搜索音乐: <code>GET /search</code></a></li>
<li><a href="/song/detail?ids=2756058128">获取音乐详情: <code>GET /song/detail</code></a></li>
<li><a href="/comment/music?id=2756058128&limit=1">获取音乐评论: <code>GET /comment/music</code></a></li>
<li><a href="/song/url/v1?id=2756058128&level=exhigh">获取音乐播放链接: <code>GET /song/url/v1</code></a></li>
</ul>
</section>
<section class="block">
<h2>调试部分</h2>
<pre><code>curl -s {origin}/inner/version
curl -s {origin}/search?keywords=网易云</code></pre>
<div style="margin-top:10px; line-height:2;">
<a href="/api.html">交互式调试</a> ·
<a href="/qrlogin.html">二维码登录示例</a> ·
<a href="/unblock_test.html">解灰测试</a> ·
<a href="/audio_match_demo/index.html">听歌识曲 Demo</a> ·
<a href="/cloud.html">云盘上传</a> ·
<a href="/playlist_import.html">歌单导入</a> ·
<a href="/eapi_decrypt.html">EAPI 解密</a> ·
<a href="/listen_together_host.html">一起听示例</a> ·
<a href="/playlist_cover_update.html">更新歌单封面示例</a> ·
<a href="/avatar_update.html">头像更新示例</a>
</div>
</section>
<footer class="site-footer">
<a href="https://github.com/neteasecloudmusicapienhanced/api-enhanced" target="_blank">GitHub</a>
</footer>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
var origin = window.location.origin;
document.getElementById('base-url').textContent = origin;
document.getElementById('current-url').textContent = window.location.href;
fetch('/inner/version', { method: 'POST' })
.then(function (r) { return r.json(); })
.then(function (data) {
var v = data && data.data && data.data.version;
if (v) document.getElementById('api-version').textContent = 'v' + v;
var pre = document.querySelector('pre code');
if (pre) pre.textContent = pre.textContent.replace(/\{origin\}/g, origin);
})
.catch(function () {});
});
</script>
</body>
</html>

View File

@ -26,6 +26,24 @@ app.get('/api/data', (req, res) => {
res.json(capturedData); res.json(capturedData);
}); });
// 版本信息端点
app.get('/api/version', (req, res) => {
try {
const packageJson = require('../../package.json');
res.json({ version: packageJson.version });
} catch (error) {
console.error('Failed to read package.json:', error);
res.json({ version: '0.1.0' });
}
});
// 清空数据端点
app.post('/api/clear', (req, res) => {
capturedData = [];
broadcastData();
res.json({ success: true });
});
// SSE 端点 // SSE 端点
app.get('/api/events', (req, res) => { app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Content-Type', 'text/event-stream');

View File

@ -301,13 +301,36 @@
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
.clear-btn {
display: inline-block;
padding: 4px 10px;
background: #f5f5f5;
color: var(--muted);
border: 1px solid var(--border);
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
margin-left: 8px;
}
.clear-btn:hover {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.clear-btn:active {
transform: scale(0.98);
}
</style> </style>
</head> </head>
<body> <body>
<div class="main-container"> <div class="main-container">
<div class="left-panel"> <div class="left-panel">
<div class="header"> <div class="header">
<h1>网易云音乐抓包工具 <span class="badge">v0.1.0</span></h1> <h1>网易云音乐抓包工具 <span class="badge" id="version-badge">加载中...</span></h1>
<p class="sub">🔍 简易网易云音乐客户端抓包工具</p> <p class="sub">🔍 简易网易云音乐客户端抓包工具</p>
</div> </div>
<div class="left-content"> <div class="left-content">
@ -330,7 +353,7 @@
</section> </section>
<div class="capture-list"> <div class="capture-list">
<h2>抓包列表</h2> <h2>抓包列表 <button class="clear-btn" onclick="clearCaptures()">清空</button></h2>
<div id="capture-list"></div> <div id="capture-list"></div>
</div> </div>
</div> </div>
@ -485,6 +508,62 @@
return div.innerHTML; return div.innerHTML;
} }
function fetchVersion() {
fetch('/api/version')
.then(response => response.json())
.then(data => {
if (data.version) {
document.getElementById('version-badge').textContent = 'v' + data.version;
} else {
document.getElementById('version-badge').textContent = 'v0.1.0';
}
})
.catch(err => {
console.error('Failed to fetch version:', err);
document.getElementById('version-badge').textContent = 'v0.1.0';
});
}
function clearCaptures() {
if (!capturedData || capturedData.length === 0) {
showToast('暂无抓包数据可清空');
return;
}
if (!confirm('确定要清空所有抓包信息吗?')) {
return;
}
capturedData = [];
selectedIndex = -1;
renderCaptureList();
// 重置右侧详情面板
const detailPanel = document.getElementById('detail-panel');
detailPanel.innerHTML = `
<div class="no-selection">
<span class="icon">📡</span>
<span>请从左侧选择一个抓包记录查看详情</span>
</div>
`;
// 向服务器发送清空请求
fetch('/api/clear', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
showToast('已清空所有抓包信息');
} else {
showToast('清空失败: ' + (data.message || '未知错误'));
}
showToast('已清空所有抓包信息');
})
.catch(err => {
console.error('Clear failed:', err);
showToast('清空失败,请重试');
});
}
function setupSSE() { function setupSSE() {
const eventSource = new EventSource('/api/events'); const eventSource = new EventSource('/api/events');
@ -495,20 +574,25 @@
// 更新状态为已连接 // 更新状态为已连接
updateStatus(); updateStatus();
// 检查是否有新数据(比较最后一个时间戳或数量) // 直接使用服务器返回的数据
const hasNewData = !capturedData.length || capturedData = data;
data.length > capturedData.length || renderCaptureList();
(data.length > 0 && capturedData.length > 0 &&
data[data.length - 1].timestamp !== capturedData[capturedData.length - 1].timestamp);
if (hasNewData) { // 如果当前没有选中且数据不为空,自动选中第一个
capturedData = data; if (selectedIndex === -1 && data.length > 0) {
renderCaptureList(); selectCapture(0);
}
// 如果是新数据且当前没有选中,自动选中第一个 // 如果选中的索引超出了数据范围,重置选中状态
if (selectedIndex === -1 && data.length > 0) { if (selectedIndex >= data.length) {
selectCapture(0); selectedIndex = -1;
} const detailPanel = document.getElementById('detail-panel');
detailPanel.innerHTML = `
<div class="no-selection">
<span class="icon">📡</span>
<span>请从左侧选择一个抓包记录查看详情</span>
</div>
`;
} }
} catch (e) { } catch (e) {
console.error('Failed to parse SSE data:', e); console.error('Failed to parse SSE data:', e);
@ -524,6 +608,9 @@
var origin = window.location.origin; var origin = window.location.origin;
document.getElementById('base-url').textContent = origin; document.getElementById('base-url').textContent = origin;
// 获取版本信息
fetchVersion();
// 设置 SSE 连接 // 设置 SSE 连接
setupSSE(); setupSSE();
}); });

View File

@ -143,6 +143,22 @@ const startClient = () => {
res.json(capturedData); res.json(capturedData);
}); });
app.get('/api/version', (req, res) => {
try {
const packageJson = require('../package.json');
res.json({ version: packageJson.version });
} catch (error) {
console.error('Failed to read package.json:', error);
res.json({ version: '0.1.0' });
}
});
app.post('/api/clear', (req, res) => {
capturedData = [];
broadcastData();
res.json({ success: true });
});
app.get('/api/events', (req, res) => { app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Cache-Control', 'no-cache');