Files
15b0b12777 0.8.0/dev (#1061)
* Initial plan

* Initial plan for hierarchical configuration system

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Implement hierarchical configuration system with platform and room-level overrides

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Add config validation, platform key mapping, and hierarchical config example

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Add enhanced GUI configuration interface with hierarchical config support

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Complete implementation with documentation and working web UI demo

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 根据反馈完善层级配置系统:移除二进制文件,更新中文界面,支持所有直播平台

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 改进配置界面:添加YAML解析库,输出模板预览和详细工具提示

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 完善监控列表界面:增加直播间详细信息展开功能

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 修复后端API支持和前端功能,添加日志查看功能

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 修复前端编译错误,调整优化package.json

* 实现真实的层级配置系统:添加日志存储,解析配置,更新所有组件使用解析后配置

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 实现层级配置系统:支持平台级设置、中文GUI界面、监控详情展示和真实数据后端

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 实现真实的平台访问频率限制功能

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 为所有主要直播平台添加访问频率限制支持

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 完成层级配置系统:平台访问频率限制功能全面实现

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 实现层级配置系统:支持平台级设置、中文GUI界面、监控详情展示、真实数据后端和统一访问频率限制

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 修复CI lint错误:添加webapp默认构建文件

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* feat: 新增ntfy通知功能

* feat: 添加停止录制时的ntfy推送通知

* 优化ntfy推送通知

* Update src/notify/ntfy/ntfy.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix test error

* 为每个直播间分别保留一组直播间自己的log信息,供前端取用。
前端监控列表的详细信息现在由SSE实时更新。
为详情页面的日志增加了一些便利功能。

* 升级antd v3 -> v6

* 升级 react-router-dom v5-> v6

* 优化层级设置UI,访问频率限制逻辑等

* code refine

* fix CI error

* Fix CI error

* Fix CI error

* Fix "Component state update uses potentially inconsistent value" issue

* 根据代码review修复问题:替换废弃方法,添加null检查,修复state更新和变量使用

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 修复CI编译错误:添加default构建文件并修正handler.go中的变量引用

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Initial plan

* Implement real-time countdown for next GetInfo request

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Add real-time download speed display for recording streams

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Fix setState callbacks to return prevState instead of null

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Use safe type assertion for RecorderManager to prevent panic

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Address PR review comments: add periodic SSE broadcast, fix optional chaining, cleanup memory leaks

- Add periodic (3s) broadcast of recorder status via SSE for real-time download speed updates
- Use optional chaining (?.) for safer recorder_status.speed access
- Remove redundant nil check for recorder in GetRecorder
- Clean up countdownTimers state when rows are collapsed to prevent memory leak
- Use Math.ceil() for countdown initialization to avoid premature "available" display
- Set up broadcast callback in server initialization to avoid circular dependencies

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Fix race condition and improve comments in status broadcaster

- Add sync.WaitGroup to ensure broadcaster goroutine exits cleanly
- Update comments to reflect callback-based implementation
- Ensure proper cleanup sequence in Close() method

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Improve error handling in recorder status retrieval

- Add warning log when GetStatus() fails for better observability
- Simplify logic by assigning status regardless of nil value
- Helps with debugging recorder status issues

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Improve countdown display and download speed formatting

- Add SSE list threshold config (default 50 rooms) in RPC settings
- Change download speed format from "0.562x" to "MB/s" or "KB/s" based on bitrate
- Update recorder status broadcast interval from 3s to 5s
- Fix countdown display: use "距离下次刷新" instead of "距下次GetInfo请求"
- Change "立即可用" to "正在刷新" for better UX
- Implement auto-calibrating countdown that adjusts with SSE updates
- Track last update times to maintain accurate countdown state

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* Add list-level SSE subscription with configurable threshold

- Enable SSE for live room list by default when room count < threshold
- Automatically switch to detail-only SSE when list exceeds threshold
- Update SSE subscription strategy dynamically when rooms added/removed
- Wildcard SSE subscription ('*') receives all live_update events
- List refreshes automatically when receiving SSE updates
- Clean up list-level subscription on component unmount

Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>

* 任务系统 init

* 新增任务列表,直播事件功能和对应数据库

* 修复新增功能前后端bug

* 暂时commit部分修改内容,还未充分检查

* 0.8.0/dev (#1059)

* 优化视频播放界面,新增退出视频播放按钮(支持ESC退出)

* 文件列表新增文件重命名和删除操作,支持批量操作

* 设置界面引入前端校验,确保前后端参数一致性,之前前端非法参数可以保存到后端config,如最小录制时长。本次修改在前端加入校验提示,非法参数直接拦截并提示有效值

* 哔哩哔哩cookie功能优化,新增扫码登录和验证机制

* 哔哩哔哩cookie界面优化

* fix code review

* 修复重命名、删除、批量操作的越权问题

* 表单验证间隔检测规则保持这里为0

* 完善报错

* 回滚误改的listening_count

* 修改文案

* 修复哔哩哔哩cookie界面api反复调用问题

* 补充console

* 重复的 ESC 键监听器问题修复

* 修复cookie潜在的内存问题

* cookie错误处理日志

* 错误日志改进

* 补充注释

* 调整文件编码转义,支持特殊字符,国际化,emojy等

* 修复--rsp是ReadableStream,只能读一次。如果 rsp.json() 失败后再去 rsp.text() 会失败。所以原始代码 clone 了一个 response 使用。

* 实现直播流选择,可选画质设置

* ci: 对齐版本、修复 lint 警告并添加开发工具模板

版本对齐:
- 将 CI 中的 Go 版本从 1.24 更新到 1.25 (匹配 go.mod)
- 将 Node.js 配置改为使用 .node-version 文件 (v20.10.0)
- 升级 actions/setup-go 到 v5, actions/setup-node 到 v4

Lint 修复:
- 修复 staticcheck 警告 (S1039, ST1023, S1009, QF1008)
- 删除未使用的函数:
  - recorder.go: renderUploadPath, logAvailableStreams, extractRoomID
  - store.go: getIntFromMap, getFloat64FromMap
  - runjob_windows.go: runWithKillOnClose
- 重新生成 mock 文件

开发环境:
- 添加 VS Code 调试配置模板 (.vscode/launch.example.json)
- 更新 .gitignore 支持本地 launch.json 自定义
- 更新 README 添加使用说明

* feat: 添加版本统计、更新器增强与开发环境改进

版本统计:
- 新增 telemetry 包,仅统计程序版本号
- 用于了解用户更新程序的整体趋势
- 不收集平台、架构或任何其他个人数据

更新器增强:
- 实现 SHA256 校验和验证,确保下载完整性
- 支持 tar.gz 和 zip 压缩包解压
- 优先使用 bililive-go.com 代理,回退到 GitHub 直连

开发环境:
- 升级 Go 版本到 1.25
- 新增 tools/devtools.go 管理开发工具依赖 (delve, gopls, staticcheck)
- Makefile 添加 help 命令
- 新增 test/ 目录和 launcher 测试基础设施

* fix: 修复前端 ESLint 警告并启用严格模式构建

ESLint 修复:
- config-info/index.tsx: 移除未使用的 useNavigate 和 addPlatformVisible
- history-panel/index.tsx: 移除未使用的 List、UserOutlined、HistoryResponse
- live-list/index.tsx: 移除未使用的 Modal

构建配置改进:
- 默认 build 命令使用 CI=true 模式,警告视为错误
- 添加 cross-env 依赖实现跨平台环境变量设置
- 添加 yarn lint 命令进行独立 ESLint 检查
- ESLint 配置中将 no-unused-vars 规则设为 error

这确保本地开发与 CI 行为一致,尽早发现代码问题

* 删除未使用的golang函数

* feat: 添加 Playwright E2E 测试框架

- 集成 Playwright 进行端到端测试
- 使用 osrp-stream-tester 作为测试流服务器
- 添加基础 UI 测试和直播录制测试用例
- 创建 GitHub Actions E2E 测试工作流
- 更新 Makefile 添加测试相关命令

使用方法:
- make install-e2e  # 安装测试依赖
- make test-e2e     # 运行 E2E 测试
- make test-e2e-ui  # 带 UI 运行测试

* fix: 添加 package-lock.json 以修复 CI

* feat: 添加 Playwright E2E 测试框架、相关测试用例和 GitHub Actions 工作流。

* ci: 新增 Playwright E2E 测试工作流。

* feat: 添加 Playwright E2E 测试 GitHub Actions 工作流。

* feat: 添加 E2E 测试的 GitHub Actions 工作流。

* feat: 添加端到端测试以及对应的 GitHub Actions CI 工作流

* feat: 新增录制功能的端到端测试

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kira1928 <2352900+kira1928@users.noreply.github.com>
Co-authored-by: July <31428852+onlyjuly@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joftal <42807405+Joftal@users.noreply.github.com>
2026-01-31 03:44:14 +09:00

268 lines
6.9 KiB
JavaScript

/**
* Playwright 报告服务器 (增强版)
*
* 使用方法:
* node scripts/report-server.js # 本地模式,从文件系统读取源码
* node scripts/report-server.js --commit abc123 # CI 模式,从 GitHub 获取源码
* npm run report # 本地模式
*
* 特性:
* - 本地模式:直接从文件系统读取源码(和 npx playwright show-report 一样)
* - CI 模式:从 GitHub 获取指定 commit 的源码
*/
const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const PORT = 9323;
const REPORT_DIR = path.join(__dirname, '..', 'playwright-report');
const PROJECT_ROOT = path.join(__dirname, '..');
// 解析命令行参数
const args = process.argv.slice(2);
let commitSha = null;
let repo = 'bililive-go/bililive-go';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--commit' && args[i + 1]) {
commitSha = args[i + 1];
i++;
} else if (args[i] === '--repo' && args[i + 1]) {
repo = args[i + 1];
i++;
}
}
// MIME 类型映射
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.zip': 'application/zip',
'.webmanifest': 'application/manifest+json',
'.webm': 'video/webm',
'.mp4': 'video/mp4',
'.ts': 'text/plain',
'.tsx': 'text/plain',
'.go': 'text/plain',
'.yaml': 'text/plain',
'.yml': 'text/plain',
'.md': 'text/plain',
};
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
/**
* 从 GitHub 获取文件内容
*/
function fetchFromGitHub(filePath, commit) {
return new Promise((resolve, reject) => {
// 将 Windows 路径转换为相对路径
let relativePath = filePath;
// 移除驱动器前缀和项目根路径
if (relativePath.includes('bililive-go')) {
const match = relativePath.match(/bililive-go[\\\/](.+)/);
if (match) {
relativePath = match[1];
}
}
// 标准化路径分隔符
relativePath = relativePath.replace(/\\/g, '/');
const url = `https://raw.githubusercontent.com/${repo}/${commit}/${relativePath}`;
console.log(`[GitHub] Fetching: ${url}`);
https.get(url, (res) => {
if (res.statusCode === 404) {
resolve(null);
return;
}
if (res.statusCode !== 200) {
reject(new Error(`GitHub returned ${res.statusCode}`));
return;
}
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
/**
* 从本地文件系统读取源码
*/
function readLocalSource(filePath) {
try {
// 尝试直接读取
if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf-8');
}
// 尝试相对于项目根目录
const relativePath = path.join(PROJECT_ROOT, filePath);
if (fs.existsSync(relativePath)) {
return fs.readFileSync(relativePath, 'utf-8');
}
return null;
} catch (e) {
console.error(`[Local] Failed to read: ${filePath}`, e.message);
return null;
}
}
// 创建 HTTP 服务器
const server = http.createServer(async (req, res) => {
// 解析请求路径
let reqPath = decodeURIComponent(req.url.split('?')[0]);
const queryString = req.url.includes('?') ? req.url.split('?')[1] : '';
const params = new URLSearchParams(queryString);
// 处理根路径
if (reqPath === '/') reqPath = '/index.html';
// 源码文件请求(Playwright Trace Viewer 使用的格式)
// 格式: /file?path=C:/path/to/file.ts
if (reqPath === '/file' && params.has('path')) {
const filePath = params.get('path');
console.log(`[Source] Request: ${filePath}`);
let content = null;
// 优先使用本地文件
content = readLocalSource(filePath);
// 如果本地没有,且指定了 commit,从 GitHub 获取
if (!content && commitSha) {
try {
content = await fetchFromGitHub(filePath, commitSha);
} catch (e) {
console.error(`[GitHub] Error: ${e.message}`);
}
}
if (content) {
res.writeHead(200, {
'Content-Type': getMimeType(filePath),
'Cache-Control': 'no-cache'
});
res.end(content);
} else {
res.writeHead(404);
res.end(`// Source file not found: ${filePath}`);
}
return;
}
// 报告文件请求
const filePath = path.join(REPORT_DIR, reqPath);
// 安全检查:确保不能访问 REPORT_DIR 之外的文件
if (!filePath.startsWith(REPORT_DIR)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
fs.stat(filePath, (err, stats) => {
if (err) {
res.writeHead(404);
res.end(`File not found: ${reqPath}`);
return;
}
if (stats.isDirectory()) {
// 如果是目录,尝试返回 index.html
const indexPath = path.join(filePath, 'index.html');
fs.readFile(indexPath, (err, data) => {
if (err) {
res.writeHead(404);
res.end('Directory index not found');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
});
return;
}
// 读取并返回文件
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end('Error reading file');
return;
}
res.writeHead(200, {
'Content-Type': getMimeType(filePath),
'Cache-Control': 'no-cache'
});
res.end(data);
});
});
});
// 检查报告目录是否存在
if (!fs.existsSync(REPORT_DIR)) {
console.error('❌ playwright-report 目录不存在!');
console.error(' 请先运行测试生成报告:npm run test:e2e');
process.exit(1);
}
// 启动服务器
server.listen(PORT, () => {
const url = `http://localhost:${PORT}`;
console.log('');
console.log('🎭 Playwright 报告服务器已启动');
console.log('');
console.log(` 📊 报告地址: ${url}`);
if (commitSha) {
console.log(` 📦 源码来源: GitHub (${repo}@${commitSha.substring(0, 7)})`);
} else {
console.log(` 📁 源码来源: 本地文件系统`);
}
console.log('');
console.log(' 按 Ctrl+C 停止服务器');
console.log('');
// 自动在浏览器中打开
const command = process.platform === 'win32'
? `start ${url}`
: process.platform === 'darwin'
? `open ${url}`
: `xdg-open ${url}`;
exec(command, (err) => {
if (err) {
console.log(` 提示: 请手动在浏览器中打开 ${url}`);
}
});
});
// 优雅关闭
process.on('SIGINT', () => {
console.log('\n\n👋 服务器已停止');
process.exit(0);
});