Compare commits

...

2 Commits

Author SHA1 Message Date
817a5572af
feats 2026-08-12 15:17:00 +08:00
9015b5dadf
refactors 2026-08-02 23:04:21 +08:00
6 changed files with 1495 additions and 1706 deletions

5
.codegraph/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# CodeGraph data files — local to each machine, not for committing.
# Ignore everything in .codegraph/ except this file itself, so transient
# files (the database, daemon.pid, sockets, logs) never show up in git.
*
!.gitignore

View File

@ -2,6 +2,7 @@ const express = require('express');
const path = require('path');
const fs = require('fs');
const axios = require('axios');
const { randomUUID } = require('crypto');
require('dotenv').config();
const CAPTURE_FILE = path.join(__dirname, '..', '..', 'captures.jsonl');
@ -9,6 +10,22 @@ const CAPTURE_FILE = path.join(__dirname, '..', '..', 'captures.jsonl');
let capturedData = [];
let clients = [];
function ensureCaptureId(data) {
if (!data || typeof data !== 'object') return data;
if (typeof data.id !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(data.id)) {
data.id = randomUUID();
}
return data;
}
function rewriteCaptureFile() {
fs.writeFileSync(
CAPTURE_FILE,
capturedData.map(data => JSON.stringify(data)).join('\n') + (capturedData.length ? '\n' : ''),
'utf-8'
);
}
// ======================== 数据持久化 ========================
/**
@ -25,7 +42,10 @@ function loadFromFile() {
try { return JSON.parse(line); }
catch { return null; }
})
.filter(Boolean);
.filter(Boolean)
.map(ensureCaptureId);
// 旧记录没有稳定 ID补齐后写回确保选择与删除跨刷新保持一致。
rewriteCaptureFile();
console.log(`Loaded ${capturedData.length} historical records from ${CAPTURE_FILE}`);
}
}
@ -177,7 +197,7 @@ function createApp() {
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/capture', (req, res) => {
const data = req.body;
const data = ensureCaptureId(req.body);
capturedData.push(data);
console.log('Captured data:', data.path);
// 持久化
@ -207,6 +227,27 @@ function createApp() {
res.json({ success: true });
});
app.post('/api/delete', (req, res) => {
const ids = Array.isArray(req.body.ids) ? [...new Set(req.body.ids.filter(id => typeof id === 'string'))] : [];
if (ids.length === 0) return res.status(400).json({ error: 'ids must be a non-empty string array' });
const idSet = new Set(ids);
const previousData = capturedData;
const nextData = capturedData.filter(item => !idSet.has(item.id));
const deleted = previousData.length - nextData.length;
try {
capturedData = nextData;
rewriteCaptureFile();
broadcastData();
res.json({ success: true, deleted });
} catch (e) {
capturedData = previousData;
console.error('Failed to persist deleted captures:', e.message);
res.status(500).json({ error: 'Failed to persist deleted captures' });
}
});
// 请求重放端点
app.post('/api/replay', async (req, res) => {
const { path: apiPath, method, params, crypto, rawPath, requestHeaders } = req.body;
@ -216,7 +257,10 @@ function createApp() {
}
try {
const result = await replayRequest({ path: apiPath, method, params, crypto, rawPath, requestHeaders });
if (!['eapi', 'linuxapi', 'api'].includes(crypto)) {
return res.status(400).json({ error: `Unsupported replay crypto: ${crypto || 'unknown'}` });
}
const result = ensureCaptureId(await replayRequest({ path: apiPath, method, params, crypto, rawPath, requestHeaders }));
// 将重放结果也加入抓包列表
capturedData.push(result);
appendToFile(result);

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
{
"name": "网易云音乐抓包工具",
"short_name": "抓包工具",
"description": "网易云音乐客户端抓包与调试工具",
"start_url": "/",
"display": "standalone",
"background_color": "#f0f2f5",
"theme_color": "#C20C0C",
"categories": ["developer tools", "utilities"],
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -1,18 +0,0 @@
// 无缓存透传 Service Worker
// 仅用于 PWA 安装能力,不缓存任何资源
self.addEventListener('install', function() {
// 立即激活,不等待页面关闭
self.skipWaiting();
});
self.addEventListener('activate', function() {
// 接管所有客户端
self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// 纯透传:不缓存任何内容,每次都请求网络
// 如果网络不可用,请求失败由浏览器自行处理
event.respondWith(fetch(event.request));
});

View File

@ -96,6 +96,7 @@ const domainList = [
'y.163.com',
'interface.music.163.com',
'interface3.music.163.com',
'interfacepc.music.163.com',
];
/**
@ -449,6 +450,7 @@ hook.request.after = (ctx) => {
duration,
requestHeaders: ctx.requestHeaders,
responseHeaders,
isNetease: true,
};
axios.post(`http://localhost:${process.env.PORT || 3000}/api/capture`, dataToSend)
.catch(err => logger.error('Failed to send data to frontend:', err));
@ -468,6 +470,7 @@ hook.request.after = (ctx) => {
duration,
requestHeaders: ctx.requestHeaders,
responseHeaders,
isNetease: true,
};
axios.post(`http://localhost:${process.env.PORT || 3000}/api/capture`, dataToSend)
.catch(err => logger.error('Failed to send data to frontend:', err));