mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-clawer.git
synced 2026-03-21 09:53:10 +00:00
feat: 支持清空抓包信息
This commit is contained in:
parent
3dea10d82f
commit
1e1111c112
125
example.html
125
example.html
@ -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>
|
|
||||||
@ -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');
|
||||||
|
|||||||
@ -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 ||
|
|
||||||
data.length > capturedData.length ||
|
|
||||||
(data.length > 0 && capturedData.length > 0 &&
|
|
||||||
data[data.length - 1].timestamp !== capturedData[capturedData.length - 1].timestamp);
|
|
||||||
|
|
||||||
if (hasNewData) {
|
|
||||||
capturedData = data;
|
capturedData = data;
|
||||||
renderCaptureList();
|
renderCaptureList();
|
||||||
|
|
||||||
// 如果是新数据且当前没有选中,自动选中第一个
|
// 如果当前没有选中且数据不为空,自动选中第一个
|
||||||
if (selectedIndex === -1 && data.length > 0) {
|
if (selectedIndex === -1 && data.length > 0) {
|
||||||
selectCapture(0);
|
selectCapture(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果选中的索引超出了数据范围,重置选中状态
|
||||||
|
if (selectedIndex >= data.length) {
|
||||||
|
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();
|
||||||
});
|
});
|
||||||
|
|||||||
16
src/index.js
16
src/index.js
@ -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');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user