/** * Koneko 音源测试平台 - 服务器 v1.1 * 端口: 1255 * 支持 SSE 实时日志推送 */ const express = require('express'); const path = require('path'); const { testPlugin } = require('./lib/tester'); const { ALL_PLATFORMS } = require('./lib/detector'); const app = express(); const PORT = 1255; app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); /** * 获取所有支持的平台信息 */ app.get('/api/platforms', (req, res) => { res.json({ success: true, platforms: ALL_PLATFORMS }); }); /** * SSE 流式测试接口 * POST /api/test/stream * body: { plugins: [{ url, code, filename }] } * 返回: text/event-stream */ app.post('/api/test/stream', async (req, res) => { try { const { plugins } = req.body; if (!plugins || !Array.isArray(plugins) || plugins.length === 0) { return res.status(400).json({ error: '请提供至少一个插件链接或文件' }); } if (plugins.length > 20) { return res.status(400).json({ error: '单次最多测试 20 个插件' }); } // SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*' }); const send = (event, data) => { res.write(`event: ${event}\n`); res.write(`data: ${JSON.stringify(data)}\n\n`); }; send('start', { total: plugins.length, time: new Date().toISOString() }); const concurrencyLimit = 3; let completed = 0; for (let i = 0; i < plugins.length; i += concurrencyLimit) { const batch = plugins.slice(i, i + concurrencyLimit); const batchResults = await Promise.all( batch.map(async (p, batchIdx) => { const globalIdx = i + batchIdx; send('plugin_start', { index: globalIdx, url: p.url || '', filename: p.filename || '' }); const logger = (level, message, data) => { send('log', { index: globalIdx, level, // info, success, warn, error message, data: data || null, time: new Date().toISOString() }); }; try { const result = await testPlugin(p.url || '', p.code || null, p.filename || '', logger); send('plugin_done', { index: globalIdx, result }); return result; } catch (err) { const errorResult = { url: p.url || p.filename, filename: p.filename || '', platform: 'unknown', platformLabel: '未知格式', metadata: {}, downloadInfo: { success: false, error: err.message }, latencyTests: [], qualityTests: [], overallStatus: 'failed', summary: { score: 0, details: { error: err.message } }, testedAt: new Date().toISOString() }; send('log', { index: globalIdx, level: 'error', message: `测试异常: ${err.message}`, time: new Date().toISOString() }); send('plugin_done', { index: globalIdx, result: errorResult }); return errorResult; } }) ); completed += batchResults.length; send('progress', { completed, total: plugins.length, percent: Math.round((completed / plugins.length) * 100) }); } send('done', { total: plugins.length, time: new Date().toISOString() }); res.end(); } catch (err) { console.error('测试出错:', err); if (!res.headersSent) { res.status(500).json({ error: '服务器内部错误: ' + err.message }); } else { res.end(); } } }); /** * 兼容旧接口 - 批量测试(非流式) */ app.post('/api/test', async (req, res) => { try { const { plugins } = req.body; if (!plugins || !Array.isArray(plugins) || plugins.length === 0) { return res.status(400).json({ error: '请提供至少一个插件链接或文件' }); } const results = []; const concurrencyLimit = 5; for (let i = 0; i < plugins.length; i += concurrencyLimit) { const batch = plugins.slice(i, i + concurrencyLimit); const batchResults = await Promise.all( batch.map(p => testPlugin(p.url || '', p.code || null, p.filename || '').catch(err => ({ url: p.url || p.filename, filename: p.filename || '', platform: 'unknown', platformLabel: '未知格式', metadata: {}, downloadInfo: { success: false, error: err.message }, latencyTests: [], qualityTests: [], overallStatus: 'failed', summary: { score: 0, details: { error: err.message } }, testedAt: new Date().toISOString() }))) ); results.push(...batchResults); } res.json({ success: true, count: results.length, results, testedAt: new Date().toISOString() }); } catch (err) { res.status(500).json({ error: '服务器内部错误: ' + err.message }); } }); /** * 导出 Markdown 表格 */ app.post('/api/export/md', (req, res) => { try { const { results } = req.body; if (!results || !Array.isArray(results)) return res.status(400).json({ error: '无效的测试结果' }); res.json({ success: true, markdown: generateMarkdownTable(results) }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.get('/api/health', (req, res) => { res.json({ status: 'ok', service: 'Koneko 音源测试平台', version: '1.1.0', time: new Date().toISOString() }); }); function generateMarkdownTable(results) { const lines = []; lines.push('# Koneko 音源测试平台 - 测试报告'); lines.push(''); lines.push(`> 测试时间: ${new Date().toLocaleString('zh-CN')}`); lines.push(`> 测试数量: ${results.length}`); lines.push(''); lines.push('## 测试概览'); lines.push(''); lines.push('| # | 插件名称 | 平台 | 版本 | 音质 | 延时(ms) | 评分 | 等级 | 状态 |'); lines.push('|---|---------|------|------|------|---------|------|------|------|'); results.forEach((r, i) => { const name = r.metadata?.name || r.filename || r.url || '未知'; const platform = r.platform === 'unknown' ? '未知' : r.platform.toUpperCase(); const version = r.metadata?.version || '-'; const quality = (r.qualityTests?.map(q => q.quality).join(', ')) || '-'; const avgLatency = r.summary?.details?.avgLatency ?? '-'; const score = r.summary?.score ?? 0; const grade = r.summary?.details?.grade ?? '-'; const status = r.overallStatus === 'passed' ? '通过' : (r.overallStatus === 'warning' ? '警告' : '失败'); lines.push(`| ${i + 1} | ${name} | ${platform} | ${version} | ${quality} | ${avgLatency} | ${score} | ${grade} | ${status} |`); }); lines.push(''); lines.push('## 详细信息'); lines.push(''); results.forEach((r, i) => { const name = r.metadata?.name || r.filename || r.url || '未知'; lines.push(`### ${i + 1}. ${name}`); lines.push(''); lines.push(`- **插件链接**: ${r.url || '文件上传'}`); lines.push(`- **平台格式**: ${r.platformLabel}`); lines.push(`- **版本**: ${r.metadata?.version || '未指定'}`); lines.push(`- **作者**: ${r.metadata?.author || '未知'}`); lines.push(`- **音质支持**: ${r.qualityTests?.map(q => `${q.quality}${q.declared ? '' : '(默认)'}`).join(', ') || '无'}`); lines.push(`- **功能列表**: ${r.metadata?.features?.join(', ') || '无'}`); lines.push(`- **评分**: ${r.summary?.score || 0}/100 (${r.summary?.details?.grade || 'D'})`); lines.push(''); if (r.latencyTests?.length > 0) { lines.push('#### 延时测试'); lines.push(''); lines.push('| 端点 | 状态码 | 延时(ms) | 状态 |'); lines.push('|------|--------|---------|------|'); for (const t of r.latencyTests) { const status = t.success ? '成功' : `失败(${t.error || ''})`; const displayUrl = t.url.length > 60 ? t.url.substring(0, 57) + '...' : t.url; lines.push(`| ${displayUrl} | ${t.status} | ${t.latency} | ${status} |`); } lines.push(''); } lines.push('---'); lines.push(''); }); return lines.join('\n'); } app.listen(PORT, '0.0.0.0', () => { console.log(`Koneko 音源测试平台 v1.1 已启动`); console.log(`服务地址: http://0.0.0.0:${PORT}`); console.log(`测试时间: ${new Date().toLocaleString('zh-CN')}`); });