diff --git a/.gitignore b/.gitignore index c6bba59..3eba158 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +/dashboard/apps/drplayer/ diff --git a/dashboard/.env.production b/dashboard/.env.production new file mode 100644 index 0000000..5d31245 --- /dev/null +++ b/dashboard/.env.production @@ -0,0 +1,7 @@ +# .env.production +# 生产环境配置 +# 子目录部署配置 - 例如部署到 /apps/ 目录 +# VITE_BASE_PATH=/apps/drplayer/ + +# 如果部署到根目录,使用以下配置: +VITE_BASE_PATH=./ \ No newline at end of file diff --git a/dashboard/.env.production.apps b/dashboard/.env.production.apps new file mode 100644 index 0000000..b251bf3 --- /dev/null +++ b/dashboard/.env.production.apps @@ -0,0 +1,3 @@ +# .env.production.apps +# 子目录部署配置 - 部署到 /apps/drplayer/ 目录 +VITE_BASE_PATH=/apps/drplayer/ \ No newline at end of file diff --git a/dashboard/.env.production.root b/dashboard/.env.production.root new file mode 100644 index 0000000..c54e8d9 --- /dev/null +++ b/dashboard/.env.production.root @@ -0,0 +1,3 @@ +# .env.production.root +# 根目录部署配置 +VITE_BASE_PATH=./ \ No newline at end of file diff --git a/dashboard/.gitignore b/dashboard/.gitignore index a547bf3..16de27c 100644 --- a/dashboard/.gitignore +++ b/dashboard/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +/dist-binary/ diff --git a/dashboard/.nvmrc b/dashboard/.nvmrc new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/dashboard/.nvmrc @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/dashboard/build-binary-optimized.js b/dashboard/build-binary-optimized.js new file mode 100644 index 0000000..641e296 --- /dev/null +++ b/dashboard/build-binary-optimized.js @@ -0,0 +1,314 @@ +#!/usr/bin/env node + +import { execSync } from 'child_process'; +import { existsSync, rmSync, cpSync, mkdirSync, readdirSync, statSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// 定义路径 +const rootDir = __dirname; +const tempServerDir = join(rootDir, 'temp-server'); +const distBinaryDir = join(rootDir, 'dist-binary'); +const distDir = join(rootDir, 'dist'); + +// 配置选项 +const config = { + // pkg优化选项 + pkg: { + compress: 'Brotli', // 使用Brotli压缩 (GZip也可选) + publicPackages: '*', // 将所有包标记为public以减少体积 + noBytecode: false, // 保持字节码生成以避免兼容性问题 + public: true, // 使用public选项加速打包 + options: 'max-old-space-size=512' // 限制内存使用 + }, + // UPX压缩选项 + upx: { + enabled: true, // 是否启用UPX压缩 + level: 6, // 压缩级别 (1-9, 降低到6以提高兼容性) + keepBackup: true, // 保留备份文件以便回滚 + ultraBrute: false, // 是否使用ultra-brute模式 + testAfterCompress: true, // 压缩后测试二进制文件 + fallbackOnError: true // 如果压缩后无法运行,则回滚到原文件 + } +}; + +console.log('🚀 开始优化打包流程...\n'); + +// 检查UPX是否可用 +function checkUpxAvailable() { + try { + execSync('upx --version', { stdio: 'pipe' }); + return true; + } catch (error) { + console.log('⚠️ UPX未安装或不可用,将跳过UPX压缩步骤'); + console.log(' 可以从 https://upx.github.io/ 下载UPX'); + return false; + } +} + +// 格式化文件大小 +function formatFileSize(bytes) { + return (bytes / 1024 / 1024).toFixed(2) + ' MB'; +} + +// 获取文件大小 +function getFileSize(filePath) { + if (!existsSync(filePath)) return 0; + return statSync(filePath).size; +} + +try { + // 步骤 1: 前端构建 + console.log('📦 步骤 1: 构建前端项目...'); + execSync('pnpm build:apps', { + stdio: 'inherit', + cwd: rootDir + }); + console.log('✅ 前端构建完成\n'); + + // 步骤 2: 清理 temp-server 中的旧文件 + console.log('🧹 步骤 2: 清理 temp-server 中的旧文件...'); + + const tempServerAppsDir = join(tempServerDir, 'apps'); + if (existsSync(tempServerAppsDir)) { + rmSync(tempServerAppsDir, { recursive: true, force: true }); + console.log('✅ 已删除旧的 apps 目录'); + } + console.log(''); + + // 步骤 3: 复制构建文件到 temp-server/apps/drplayer + console.log('📁 步骤 3: 复制构建文件到 temp-server...'); + if (!existsSync(distDir)) { + throw new Error('dist 目录不存在,请确保前端构建成功'); + } + + if (!existsSync(tempServerDir)) { + mkdirSync(tempServerDir, { recursive: true }); + } + + const tempServerDrplayerDir = join(tempServerAppsDir, 'drplayer'); + if (!existsSync(tempServerAppsDir)) { + mkdirSync(tempServerAppsDir, { recursive: true }); + } + + cpSync(distDir, tempServerDrplayerDir, { recursive: true }); + console.log('✅ 已将 dist 内容复制到 apps/drplayer'); + console.log(''); + + // 步骤 4: 在 temp-server 目录中打包二进制文件(使用优化选项) + console.log('⚙️ 步骤 4: 使用优化选项打包二进制文件...'); + + const tempServerNodeModules = join(tempServerDir, 'node_modules'); + if (!existsSync(tempServerNodeModules)) { + console.log('📦 安装 temp-server 依赖...'); + execSync('pnpm install', { + stdio: 'inherit', + cwd: tempServerDir + }); + } + + // 构建优化的pkg命令 + let pkgCommand = 'pkg . --target node18-win-x64 --output dist-binary/drplayer-server-win.exe'; + + // 添加压缩选项 + if (config.pkg.compress) { + pkgCommand += ` --compress ${config.pkg.compress}`; + } + + // 添加public packages选项 + if (config.pkg.publicPackages) { + pkgCommand += ` --public-packages "${config.pkg.publicPackages}"`; + } + + // 添加public选项 + if (config.pkg.public) { + pkgCommand += ' --public'; + } + + // 添加no-bytecode选项(仅在明确启用时) + if (config.pkg.noBytecode) { + pkgCommand += ' --no-bytecode'; + } + + // 添加Node.js选项 + if (config.pkg.options) { + pkgCommand += ` --options "${config.pkg.options}"`; + } + + console.log(`🔧 执行命令: ${pkgCommand}`); + execSync(pkgCommand, { + stdio: 'inherit', + cwd: tempServerDir + }); + console.log('✅ 优化二进制文件打包完成\n'); + + // 步骤 5: 移动二进制文件到 dist-binary 目录 + console.log('📦 步骤 5: 移动二进制文件到 dist-binary...'); + + if (!existsSync(distBinaryDir)) { + mkdirSync(distBinaryDir, { recursive: true }); + } + + const tempDistBinaryDir = join(tempServerDir, 'dist-binary'); + if (existsSync(tempDistBinaryDir)) { + const files = readdirSync(tempDistBinaryDir); + for (const file of files) { + const srcPath = join(tempDistBinaryDir, file); + const destPath = join(distBinaryDir, file); + + if (existsSync(destPath)) { + rmSync(destPath, { force: true }); + } + + cpSync(srcPath, destPath, { recursive: true }); + console.log(`✅ 已移动: ${file}`); + } + + try { + rmSync(tempDistBinaryDir, { recursive: true, force: true }); + } catch (error) { + console.log(`⚠️ 无法删除临时目录: ${error.message}`); + } + } + + // 步骤 6: UPX压缩(如果启用且可用) + if (config.upx.enabled && checkUpxAvailable()) { + console.log('\n🗜️ 步骤 6: 使用UPX进行额外压缩...'); + + const files = readdirSync(distBinaryDir).filter(file => file.endsWith('.exe')); + + for (const file of files) { + const filePath = join(distBinaryDir, file); + const originalSize = getFileSize(filePath); + + // 创建备份(如果启用) + if (config.upx.keepBackup) { + const backupPath = join(distBinaryDir, file + '.backup'); + cpSync(filePath, backupPath); + console.log(`📋 已创建备份: ${file}.backup`); + } + + try { + let upxCommand = `upx -${config.upx.level}`; + + // 添加额外的UPX选项以获得更好的压缩率 + if (config.upx.ultraBrute) { + upxCommand += ' --ultra-brute'; // 尝试所有压缩方法(很慢但压缩率最高) + } + + // 添加兼容性选项 + upxCommand += ' --force'; // 强制压缩 + upxCommand += ` "${filePath}"`; + + console.log(`🔧 压缩 ${file}...`); + execSync(upxCommand, { stdio: 'pipe' }); + + const compressedSize = getFileSize(filePath); + const compressionRatio = ((originalSize - compressedSize) / originalSize * 100).toFixed(1); + + console.log(`✅ ${file} 压缩完成:`); + console.log(` 原始大小: ${formatFileSize(originalSize)}`); + console.log(` 压缩后: ${formatFileSize(compressedSize)}`); + console.log(` 压缩率: ${compressionRatio}%`); + + // 测试压缩后的二进制文件 + if (config.upx.testAfterCompress) { + console.log(`🧪 测试压缩后的二进制文件...`); + try { + // 简单的启动测试 - 运行 --help 命令 + execSync(`"${filePath}" --help`, { timeout: 10000, stdio: 'pipe' }); + console.log(`✅ 压缩后的二进制文件测试通过`); + } catch (testError) { + console.log(`⚠️ 压缩后的二进制文件测试失败: ${testError.message}`); + + if (config.upx.fallbackOnError) { + console.log(`🔄 回滚到原始文件...`); + const backupPath = join(distBinaryDir, file + '.backup'); + if (existsSync(backupPath)) { + cpSync(backupPath, filePath); + console.log(`✅ 已回滚到原始未压缩文件`); + console.log(`📝 建议: pkg生成的二进制文件可能与UPX不兼容,使用原始文件`); + } + } else { + throw testError; + } + } + } + + } catch (error) { + console.log(`❌ UPX压缩 ${file} 失败: ${error.message}`); + + // 如果压缩失败且有备份,恢复原文件 + if (config.upx.keepBackup) { + const backupPath = join(distBinaryDir, file + '.backup'); + if (existsSync(backupPath)) { + cpSync(backupPath, filePath); + console.log(`🔄 已从备份恢复 ${file}`); + } + } + } + } + } else if (config.upx.enabled) { + console.log('\n⚠️ 跳过UPX压缩步骤(UPX不可用)'); + } + + // 步骤 7: 清理临时文件 + console.log('\n🧹 步骤 7: 清理临时文件...'); + + const dirsToClean = [ + join(tempServerDir, 'apps'), + join(tempServerDir, 'dist-binary') + ]; + + for (const dirPath of dirsToClean) { + if (existsSync(dirPath)) { + try { + rmSync(dirPath, { recursive: true, force: true }); + console.log(`✅ 已清理: ${dirPath.replace(tempServerDir, 'temp-server')}`); + } catch (error) { + console.log(`⚠️ 无法清理目录: ${error.message}`); + } + } + } + + // 清理UPX备份文件(可选) + if (!config.upx.keepBackup) { + const backupFiles = readdirSync(distBinaryDir).filter(file => file.endsWith('.backup')); + if (backupFiles.length > 0) { + console.log('\n🗑️ 清理备份文件...'); + for (const backupFile of backupFiles) { + const backupPath = join(distBinaryDir, backupFile); + rmSync(backupPath, { force: true }); + console.log(`✅ 已删除备份: ${backupFile}`); + } + } + } + + console.log('\n🎉 优化打包流程完成!'); + console.log(`📁 二进制文件位置: ${distBinaryDir}`); + + // 显示最终结果 + if (existsSync(distBinaryDir)) { + const files = readdirSync(distBinaryDir); + if (files.length > 0) { + console.log('\n📋 最终生成的文件:'); + let totalSize = 0; + files.forEach(file => { + const filePath = join(distBinaryDir, file); + const stats = statSync(filePath); + const size = stats.size; + totalSize += size; + console.log(` - ${file} (${formatFileSize(size)})`); + }); + console.log(`\n📊 总大小: ${formatFileSize(totalSize)}`); + } + } + +} catch (error) { + console.error('\n❌ 优化打包过程中出现错误:'); + console.error(error.message); + process.exit(1); +} \ No newline at end of file diff --git a/dashboard/build-binary.js b/dashboard/build-binary.js new file mode 100644 index 0000000..c815858 --- /dev/null +++ b/dashboard/build-binary.js @@ -0,0 +1,157 @@ +#!/usr/bin/env node + +import { execSync } from 'child_process'; +import { existsSync, rmSync, cpSync, mkdirSync, readdirSync, statSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// 定义路径 +const rootDir = __dirname; +const tempServerDir = join(rootDir, 'temp-server'); +const distBinaryDir = join(rootDir, 'dist-binary'); +const distDir = join(rootDir, 'dist'); +const tempServerDistDir = join(tempServerDir, 'dist'); + +console.log('🚀 开始自动化打包流程...\n'); + +try { + // 步骤 1: 前端构建 + console.log('📦 步骤 1: 构建前端项目...'); + execSync('pnpm build:apps', { + stdio: 'inherit', + cwd: rootDir + }); + console.log('✅ 前端构建完成\n'); + + // 步骤 2: 清理 temp-server 中的旧文件 + console.log('🧹 步骤 2: 清理 temp-server 中的旧文件...'); + + // 清理 temp-server 中的旧 apps 目录 + const tempServerAppsDir = join(tempServerDir, 'apps'); + if (existsSync(tempServerAppsDir)) { + rmSync(tempServerAppsDir, { recursive: true, force: true }); + console.log('✅ 已删除旧的 apps 目录'); + } + console.log(''); + + // 步骤 3: 复制构建文件到 temp-server/apps/drplayer + console.log('📁 步骤 3: 复制构建文件到 temp-server...'); + if (!existsSync(distDir)) { + throw new Error('dist 目录不存在,请确保前端构建成功'); + } + + // 确保 temp-server 目录存在 + if (!existsSync(tempServerDir)) { + mkdirSync(tempServerDir, { recursive: true }); + } + + // 创建 apps/drplayer 目录并复制 dist 内容 + const tempServerDrplayerDir = join(tempServerAppsDir, 'drplayer'); + if (!existsSync(tempServerAppsDir)) { + mkdirSync(tempServerAppsDir, { recursive: true }); + } + + // 将 dist 目录的内容复制到 apps/drplayer + cpSync(distDir, tempServerDrplayerDir, { recursive: true }); + console.log('✅ 已将 dist 内容复制到 apps/drplayer'); + console.log(''); + + // 步骤 4: 在 temp-server 目录中打包二进制文件 + console.log('⚙️ 步骤 4: 打包二进制文件...'); + + // 确保 temp-server 有 node_modules + const tempServerNodeModules = join(tempServerDir, 'node_modules'); + if (!existsSync(tempServerNodeModules)) { + console.log('📦 安装 temp-server 依赖...'); + execSync('pnpm install', { + stdio: 'inherit', + cwd: tempServerDir + }); + } + + // 执行 pkg 打包 + execSync('pnpm pkg:win', { + stdio: 'inherit', + cwd: tempServerDir + }); + console.log('✅ 二进制文件打包完成\n'); + + // 步骤 5: 移动二进制文件到 dist-binary 目录 + console.log('📦 步骤 5: 移动二进制文件到 dist-binary...'); + + // 确保 dist-binary 目录存在 + if (!existsSync(distBinaryDir)) { + mkdirSync(distBinaryDir, { recursive: true }); + } + + // 查找并移动二进制文件 + const tempDistBinaryDir = join(tempServerDir, 'dist-binary'); + if (existsSync(tempDistBinaryDir)) { + const files = readdirSync(tempDistBinaryDir); + for (const file of files) { + const srcPath = join(tempDistBinaryDir, file); + const destPath = join(distBinaryDir, file); + + // 如果目标文件已存在,先删除 + if (existsSync(destPath)) { + rmSync(destPath, { force: true }); + } + + // 移动文件 + cpSync(srcPath, destPath, { recursive: true }); + console.log(`✅ 已移动: ${file}`); + } + + // 清理 temp-server 中的 dist-binary 目录 + try { + rmSync(tempDistBinaryDir, { recursive: true, force: true }); + } catch (error) { + console.log(`⚠️ 无法删除临时目录 (可能有进程正在使用): ${error.message}`); + } + } + + // 步骤 6: 清理 temp-server 目录 + console.log('\n🧹 步骤 6: 清理 temp-server 临时文件...'); + + // 需要清理的目录列表 + const dirsToClean = [ + join(tempServerDir, 'apps'), + join(tempServerDir, 'dist-binary') + ]; + + for (const dirPath of dirsToClean) { + if (existsSync(dirPath)) { + try { + rmSync(dirPath, { recursive: true, force: true }); + console.log(`✅ 已清理: ${dirPath.replace(tempServerDir, 'temp-server')}`); + } catch (error) { + console.log(`⚠️ 无法清理目录 ${dirPath.replace(tempServerDir, 'temp-server')}: ${error.message}`); + } + } + } + + console.log('\n🎉 自动化打包流程完成!'); + console.log(`📁 二进制文件位置: ${distBinaryDir}`); + + // 显示生成的文件 + if (existsSync(distBinaryDir)) { + const files = readdirSync(distBinaryDir); + if (files.length > 0) { + console.log('\n📋 生成的文件:'); + files.forEach(file => { + const filePath = join(distBinaryDir, file); + const stats = statSync(filePath); + const size = (stats.size / 1024 / 1024).toFixed(2); + console.log(` - ${file} (${size} MB)`); + }); + } + } + +} catch (error) { + console.error('\n❌ 打包过程中出现错误:'); + console.error(error.message); + process.exit(1); +} \ No newline at end of file diff --git a/dashboard/docs/API_DOCS.md b/dashboard/docs/API_DOCS.md new file mode 100644 index 0000000..4644237 --- /dev/null +++ b/dashboard/docs/API_DOCS.md @@ -0,0 +1,556 @@ +# Pup Sniffer API 完整文档 + +## 📖 概述 + +Pup Sniffer 是一个基于 Puppeteer 和 Fastify 的视频资源嗅探服务,提供了强大的网页内容抓取和视频链接嗅探功能。本文档将详细介绍所有 API 接口的使用方法,包含完整的代码示例。 + +**服务地址**: `http://localhost:57573` + +## 🚀 快速开始 + +### 启动服务 + +```bash +# 方式一:使用 npm +npm start + +# 方式二:直接运行二进制文件 +./pup-sniffer-win.exe # Windows +./pup-sniffer-linux # Linux +./pup-sniffer-macos # macOS +``` + +### 验证服务状态 + +```bash +curl http://localhost:57573/health +``` + +## 📋 API 接口列表 + +| 接口 | 方法 | 路径 | 功能 | +|------|------|------|------| +| 首页 | GET | `/` | 演示页面 | +| 视频嗅探 | GET | `/sniffer` | 嗅探视频资源链接 | +| 页面源码 | GET | `/fetCodeByWebView` | 获取页面源码 | +| 健康检查 | GET | `/health` | 服务状态检查 | +| 活跃状态 | GET | `/active` | 浏览器状态检查 | + +--- + +## 🎯 1. 视频嗅探接口 + +### 基本信息 + +- **接口路径**: `/sniffer` +- **请求方法**: `GET` +- **功能**: 嗅探指定页面中的视频资源链接(m3u8、mp4等) + +### 请求参数 + +| 参数名 | 类型 | 必需 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `url` | string | ✅ | - | 要嗅探的页面URL | +| `mode` | string | ❌ | "0" | 嗅探模式:0=单个链接,1=多个链接 | +| `is_pc` | string | ❌ | "0" | 设备模拟:0=移动设备,1=PC | +| `timeout` | string | ❌ | "10000" | 超时时间(毫秒) | +| `css` | string | ❌ | - | 等待的CSS选择器 | +| `script` | string | ❌ | - | 页面执行脚本(Base64编码) | +| `init_script` | string | ❌ | - | 页面初始化脚本(Base64编码) | +| `headers` | string | ❌ | - | 自定义请求头(换行分隔) | +| `custom_regex` | string | ❌ | - | 自定义匹配正则表达式 | +| `sniffer_exclude` | string | ❌ | - | 排除匹配的正则表达式 | + +### 响应格式 + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "success", + "data": { + "url": "https://example.com/video.m3u8", + "headers": { + "referer": "https://example.com", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }, + "from": "https://example.com/play", + "cost": "2345 ms", + "code": 200, + "msg": "超级嗅探解析成功" + }, + "timestamp": 1703123456789 +} +``` + +#### 错误响应 + +```json +{ + "code": 400, + "msg": "URL参数不能为空", + "data": null, + "timestamp": 1703123456789 +} +``` + +### 使用示例 + +#### 1. 基础嗅探 + +```bash +curl "http://localhost:57573/sniffer?url=https://example.com/play" +``` + +```javascript +// JavaScript 示例 +async function basicSniffer() { + const response = await fetch('http://localhost:57573/sniffer?url=https://example.com/play'); + const result = await response.json(); + + if (result.code === 200) { + console.log('视频链接:', result.data.url); + console.log('请求头:', result.data.headers); + } else { + console.error('嗅探失败:', result.msg); + } +} +``` + +```python +# Python 示例 +import requests + +def basic_sniffer(): + url = "http://localhost:57573/sniffer" + params = { + "url": "https://example.com/play" + } + + response = requests.get(url, params=params) + result = response.json() + + if result["code"] == 200: + print(f"视频链接: {result['data']['url']}") + print(f"请求头: {result['data']['headers']}") + else: + print(f"嗅探失败: {result['msg']}") +``` + +#### 2. 多链接嗅探 + +```bash +curl "http://localhost:57573/sniffer?url=https://example.com/play&mode=1&timeout=15000" +``` + +```javascript +// JavaScript 多链接嗅探 +async function multiSniffer() { + const params = new URLSearchParams({ + url: 'https://example.com/play', + mode: '1', + timeout: '15000' + }); + + const response = await fetch(`http://localhost:57573/sniffer?${params}`); + const result = await response.json(); + + if (result.code === 200 && Array.isArray(result.data)) { + result.data.forEach((video, index) => { + console.log(`视频 ${index + 1}:`, video.url); + }); + } +} +``` + +#### 3. PC模式嗅探 + +```bash +curl "http://localhost:57573/sniffer?url=https://example.com/play&is_pc=1" +``` + +#### 4. 自定义脚本嗅探 + +```bash +# 先将脚本进行Base64编码 +script_b64=$(echo "document.querySelector('video').play()" | base64) +curl "http://localhost:57573/sniffer?url=https://example.com/play&script=$script_b64" +``` + +```javascript +// JavaScript 自定义脚本示例 +async function customScriptSniffer() { + const script = "document.querySelector('video').play()"; + const scriptB64 = btoa(script); // Base64编码 + + const params = new URLSearchParams({ + url: 'https://example.com/play', + script: scriptB64 + }); + + const response = await fetch(`http://localhost:57573/sniffer?${params}`); + const result = await response.json(); + return result; +} +``` + +#### 5. 自定义请求头 + +```javascript +// 自定义请求头示例 +async function customHeadersSniffer() { + const headers = [ + 'User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', + 'Referer: https://example.com', + 'Authorization: Bearer your-token' + ].join('\n'); + + const params = new URLSearchParams({ + url: 'https://example.com/play', + headers: headers + }); + + const response = await fetch(`http://localhost:57573/sniffer?${params}`); + return await response.json(); +} +``` + +--- + +## 📄 2. 页面源码获取接口 + +### 基本信息 + +- **接口路径**: `/fetCodeByWebView` +- **请求方法**: `GET` +- **功能**: 获取指定页面的完整HTML源码 + +### 请求参数 + +| 参数名 | 类型 | 必需 | 默认值 | 说明 | +|--------|------|------|--------|------| +| `url` | string | ✅ | - | 要获取源码的页面URL | +| `is_pc` | string | ❌ | "0" | 设备模拟:0=移动设备,1=PC | +| `timeout` | string | ❌ | "10000" | 超时时间(毫秒) | +| `css` | string | ❌ | - | 等待的CSS选择器 | +| `script` | string | ❌ | - | 页面执行脚本(Base64编码) | +| `init_script` | string | ❌ | - | 页面初始化脚本(Base64编码) | +| `headers` | string | ❌ | - | 自定义请求头(换行分隔) | + +### 响应格式 + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "success", + "data": { + "content": "......", + "headers": { + "location": "https://example.com", + "content-type": "text/html" + }, + "cost": "1234 ms" + }, + "timestamp": 1703123456789 +} +``` + +### 使用示例 + +#### 1. 基础源码获取 + +```bash +curl "http://localhost:57573/fetCodeByWebView?url=https://example.com" +``` + +```javascript +// JavaScript 示例 +async function getPageSource() { + const response = await fetch('http://localhost:57573/fetCodeByWebView?url=https://example.com'); + const result = await response.json(); + + if (result.code === 200) { + console.log('页面源码:', result.data.content); + console.log('响应头:', result.data.headers); + console.log('耗时:', result.data.cost); + } +} +``` + +```python +# Python 示例 +import requests + +def get_page_source(): + url = "http://localhost:57573/fetCodeByWebView" + params = {"url": "https://example.com"} + + response = requests.get(url, params=params) + result = response.json() + + if result["code"] == 200: + print(f"页面源码长度: {len(result['data']['content'])}") + print(f"耗时: {result['data']['cost']}") + + # 保存到文件 + with open('page_source.html', 'w', encoding='utf-8') as f: + f.write(result['data']['content']) +``` + +#### 2. 等待特定元素加载 + +```javascript +// 等待特定CSS选择器出现 +async function waitForElement() { + const params = new URLSearchParams({ + url: 'https://example.com', + css: '.video-container', // 等待视频容器加载 + timeout: '20000' + }); + + const response = await fetch(`http://localhost:57573/fetCodeByWebView?${params}`); + return await response.json(); +} +``` + +--- + +## ❤️ 3. 健康检查接口 + +### 基本信息 + +- **接口路径**: `/health` +- **请求方法**: `GET` +- **功能**: 检查服务运行状态 + +### 响应格式 + +```json +{ + "code": 200, + "msg": "success", + "data": { + "status": "ok", + "service": "pup-sniffer" + }, + "timestamp": 1703123456789 +} +``` + +### 使用示例 + +```bash +curl http://localhost:57573/health +``` + +```javascript +// 服务状态监控 +async function checkServiceHealth() { + try { + const response = await fetch('http://localhost:57573/health'); + const result = await response.json(); + + if (result.code === 200 && result.data.status === 'ok') { + console.log('✅ 服务运行正常'); + return true; + } else { + console.log('❌ 服务状态异常'); + return false; + } + } catch (error) { + console.log('❌ 服务连接失败:', error.message); + return false; + } +} +``` + +--- + +## 🔄 4. 活跃状态接口 + +### 基本信息 + +- **接口路径**: `/active` +- **请求方法**: `GET` +- **功能**: 检查浏览器实例状态 + +### 响应格式 + +```json +{ + "code": 200, + "msg": "success", + "data": { + "active": true, + "browser": "initialized", + "timestamp": "2023-12-21T10:30:56.789Z" + }, + "timestamp": 1703123456789 +} +``` + +### 使用示例 + +```bash +curl http://localhost:57573/active +``` + +--- + +## 🛠️ 高级用法 + +### 1. 批量嗅探 + +```javascript +// 批量嗅探多个URL +async function batchSniffer(urls) { + const results = []; + + for (const url of urls) { + try { + const response = await fetch(`http://localhost:57573/sniffer?url=${encodeURIComponent(url)}`); + const result = await response.json(); + results.push({ + url: url, + success: result.code === 200, + data: result.data + }); + } catch (error) { + results.push({ + url: url, + success: false, + error: error.message + }); + } + + // 避免请求过于频繁 + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + return results; +} + +// 使用示例 +const urls = [ + 'https://example1.com/play', + 'https://example2.com/video', + 'https://example3.com/watch' +]; + +batchSniffer(urls).then(results => { + console.log('批量嗅探结果:', results); +}); +``` + +### 2. 错误处理和重试 + +```javascript +// 带重试机制的嗅探函数 +async function snifferWithRetry(url, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + const response = await fetch(`http://localhost:57573/sniffer?url=${encodeURIComponent(url)}&timeout=15000`); + const result = await response.json(); + + if (result.code === 200) { + return result; + } else if (i === maxRetries - 1) { + throw new Error(`嗅探失败: ${result.msg}`); + } + } catch (error) { + if (i === maxRetries - 1) { + throw error; + } + console.log(`第 ${i + 1} 次尝试失败,${2 ** i} 秒后重试...`); + await new Promise(resolve => setTimeout(resolve, 2 ** i * 1000)); + } + } +} +``` + +### 3. 结果缓存 + +```javascript +// 简单的内存缓存实现 +class SnifferCache { + constructor(ttl = 300000) { // 5分钟缓存 + this.cache = new Map(); + this.ttl = ttl; + } + + get(url) { + const item = this.cache.get(url); + if (item && Date.now() - item.timestamp < this.ttl) { + return item.data; + } + this.cache.delete(url); + return null; + } + + set(url, data) { + this.cache.set(url, { + data: data, + timestamp: Date.now() + }); + } + + async sniffer(url) { + // 先检查缓存 + const cached = this.get(url); + if (cached) { + console.log('使用缓存结果'); + return cached; + } + + // 发起新请求 + const response = await fetch(`http://localhost:57573/sniffer?url=${encodeURIComponent(url)}`); + const result = await response.json(); + + // 缓存成功结果 + if (result.code === 200) { + this.set(url, result); + } + + return result; + } +} + +// 使用示例 +const cache = new SnifferCache(); +cache.sniffer('https://example.com/play').then(result => { + console.log('嗅探结果:', result); +}); +``` + +## 🚨 错误码说明 + +| 错误码 | 说明 | 解决方案 | +|--------|------|----------| +| 400 | 请求参数错误 | 检查必需参数是否提供 | +| 500 | 服务器内部错误 | 检查服务器日志,重启服务 | +| 504 | 请求超时 | 增加timeout参数值 | + +## 💡 最佳实践 + +1. **合理设置超时时间**: 根据目标网站的加载速度设置合适的timeout值 +2. **使用适当的设备模拟**: 某些网站对PC和移动端返回不同内容 +3. **处理异步加载**: 使用css参数等待关键元素加载完成 +4. **错误处理**: 始终检查返回的code字段,妥善处理错误情况 +5. **避免频繁请求**: 在批量操作时添加适当的延迟 +6. **缓存结果**: 对于相同URL的重复请求,考虑使用缓存 + +## 📞 技术支持 + +如果在使用过程中遇到问题,请: + +1. 检查服务是否正常运行(访问 `/health` 接口) +2. 查看服务器控制台日志 +3. 确认目标URL是否可正常访问 +4. 检查网络连接和防火墙设置 + +--- + +*本文档持续更新中,如有疑问请参考项目README或提交Issue。* \ No newline at end of file diff --git a/dashboard/docs/API_REFACTOR_SUMMARY.md b/dashboard/docs/API_REFACTOR_SUMMARY.md new file mode 100644 index 0000000..0e827b5 --- /dev/null +++ b/dashboard/docs/API_REFACTOR_SUMMARY.md @@ -0,0 +1,173 @@ +# API 重构总结 + +## 重构概述 + +本次重构将原本分散在Vue组件中的API调用统一封装为专门的API服务模块,提高了代码的可维护性、可复用性和可测试性。 + +## 完成的工作 + +### 1. 分析后端API文档 ✅ +- 详细分析了 `docs/apidoc.md` 和 `docs/t4api.md` +- 理解了三个主要接口:模块数据接口(T4)、模块代理接口、解析接口 +- 掌握了各接口的参数、功能和响应格式 + +### 2. 设计API封装架构 ✅ +创建了分层的API架构: +``` +src/api/ +├── index.js # 统一入口 +├── config.js # 配置和常量 +├── request.js # Axios封装 +├── modules/ # 基础API模块 +├── services/ # 业务服务层 +├── utils/ # 工具函数 +└── types/ # 类型定义 +``` + +### 3. 实现基础API工具类 ✅ + +#### 核心文件: +- **config.js**: API配置、路径、常量定义 +- **request.js**: Axios封装,包含拦截器和错误处理 +- **utils/index.js**: 数据处理和验证工具函数 +- **types/index.js**: 数据类型定义和工厂函数 + +#### 基础API模块: +- **modules/module.js**: T4模块数据接口封装 +- **modules/proxy.js**: 模块代理接口封装 +- **modules/parse.js**: 视频解析接口封装 + +### 4. 实现具体业务API模块 ✅ + +#### 业务服务层: +- **services/video.js**: 视频相关业务逻辑 + - 推荐视频获取 + - 分类视频获取 + - 视频搜索 + - 视频详情 + - 播放地址获取 + - 视频解析 + - 5分钟缓存机制 + +- **services/site.js**: 站点管理业务逻辑 + - 站点配置管理 + - 当前站点切换 + - 站点CRUD操作 + - 本地存储持久化 + +### 5. 重构现有Vue组件 ✅ + +#### 重构的组件: +1. **Video2.vue** + - 替换 `req.get` 为 `siteService` 和 `videoService` + - 优化站点配置获取逻辑 + - 改进分类列表获取方式 + +2. **VideoList.vue** + - 使用 `videoService` 获取视频列表 + - 支持推荐和分类视频加载 + - 统一错误处理 + +3. **Video.vue** + - 集成 `siteService` 进行站点管理 + - 使用 `videoService` 进行视频搜索 + - 同步多个状态管理store + +## 技术改进 + +### 1. 统一的错误处理 +- 所有API调用都有统一的错误格式 +- 自动处理HTTP状态码和业务错误码 +- 友好的错误信息提示 + +### 2. 缓存机制 +- 视频服务实现5分钟缓存 +- 减少重复请求,提高性能 +- 自动缓存清理机制 + +### 3. 数据格式化 +- 统一的数据结构定义 +- 自动数据格式化和验证 +- 类型安全的数据处理 + +### 4. 配置管理 +- 集中的API配置管理 +- 环境变量支持 +- 灵活的参数配置 + +### 5. 拦截器机制 +- 自动添加认证token +- 统一的请求头设置 +- 响应数据预处理 + +## 代码质量提升 + +### 1. 可维护性 +- 分层架构,职责清晰 +- 统一的代码风格 +- 完善的错误处理 + +### 2. 可复用性 +- 模块化设计 +- 通用的工具函数 +- 标准化的接口定义 + +### 3. 可测试性 +- 独立的服务模块 +- 纯函数设计 +- 依赖注入支持 + +### 4. 可扩展性 +- 插件化架构 +- 配置驱动 +- 标准化的扩展点 + +## 使用方式对比 + +### 重构前: +```javascript +import req from '@/utils/req' + +// 分散的API调用 +const response = await req.get('/home') +const data = response.data +``` + +### 重构后: +```javascript +import { videoService, siteService } from '@/api/services' + +// 语义化的业务方法 +const currentSite = siteService.getCurrentSite() +const data = await videoService.getRecommendVideos(currentSite.key, { + extend: currentSite.ext +}) +``` + +## 项目状态 + +✅ **开发服务器运行正常** - http://localhost:5174/ +✅ **无编译错误** +✅ **所有组件重构完成** +✅ **API封装架构完整** + +## 后续建议 + +1. **添加单元测试**: 为API服务模块编写测试用例 +2. **性能监控**: 添加API调用性能监控 +3. **文档完善**: 补充更多使用示例和最佳实践 +4. **类型定义**: 考虑使用TypeScript增强类型安全 +5. **错误上报**: 集成错误监控和上报机制 + +## 文档 + +- **API使用说明**: `src/api/README.md` +- **重构总结**: `API_REFACTOR_SUMMARY.md` (本文档) + +--- + +**重构完成时间**: 2024年1月 +**重构范围**: 前端API调用层完整重构 +**影响组件**: Video2.vue, VideoList.vue, Video.vue +**新增文件**: 15个API相关文件 +**代码质量**: 显著提升 \ No newline at end of file diff --git a/dashboard/docs/BUILD_BINARY_GUIDE.md b/dashboard/docs/BUILD_BINARY_GUIDE.md new file mode 100644 index 0000000..52e0323 --- /dev/null +++ b/dashboard/docs/BUILD_BINARY_GUIDE.md @@ -0,0 +1,231 @@ +# DrPlayer 二进制打包指南 + +## 概述 + +本指南介绍如何将 DrPlayer Dashboard 打包为跨平台的独立可执行文件。 + +## 主要改进 + +### 1. 端口配置优化 +- **默认端口**: 从 8008 改为 9978 +- **智能端口检测**: 如果端口被占用,自动尝试下一个端口 (9979, 9980, ...) +- **最大尝试次数**: 100个端口,确保能找到可用端口 + +### 2. PKG 兼容性优化 +- **环境检测**: 自动检测是否在 PKG 环境中运行 +- **路径处理**: PKG 环境中使用 `process.cwd()` 而非 `__dirname` +- **构建跳过**: PKG 环境中跳过前端构建步骤 +- **错误处理**: PKG 环境中更宽松的错误处理 + +## 打包方法 + +### 方法一:使用自动化脚本 (推荐) + +#### Windows PowerShell +```powershell +# 运行 PowerShell 脚本 +pnpm run build:binary:win + +# 或直接运行 +powershell -ExecutionPolicy Bypass -File build-binary.ps1 +``` + +#### Node.js 脚本 (跨平台) +```bash +# 运行 Node.js 脚本 +pnpm run build:binary + +# 或直接运行 +node build-binary.js +``` + +### 方法二:手动打包 + +#### 1. 安装 PKG +```bash +npm install -g pkg +``` + +#### 2. 构建前端资源 +```bash +pnpm build:fastify +``` + +#### 3. 打包指定平台 +```bash +# Windows x64 +pnpm run pkg:win + +# Linux x64 +pnpm run pkg:linux + +# macOS x64 +pnpm run pkg:macos + +# 所有平台 +pnpm run pkg:all +``` + +### 方法三:自定义打包 +```bash +# 基本命令 +pkg production-server.js --target node18-win-x64 --output drplayer-server.exe + +# 带压缩 +pkg production-server.js --target node18-win-x64 --output drplayer-server.exe --compress Brotli + +# 多平台 +pkg production-server.js --targets node18-win-x64,node18-linux-x64,node18-macos-x64 +``` + +## 支持的平台 + +| 平台 | 架构 | 输出文件名 | +|------|------|------------| +| Windows | x64 | drplayer-server-win-x64.exe | +| Linux | x64 | drplayer-server-linux-x64 | +| macOS | x64 | drplayer-server-macos-x64 | +| macOS | ARM64 | drplayer-server-macos-arm64 | + +## 输出文件结构 + +``` +dist-binary/ +├── drplayer-server-win-x64.exe # Windows 可执行文件 +├── drplayer-server-linux-x64 # Linux 可执行文件 +├── drplayer-server-macos-x64 # macOS Intel 可执行文件 +├── drplayer-server-macos-arm64 # macOS Apple Silicon 可执行文件 +├── start-windows.bat # Windows 启动脚本 +├── start-linux.sh # Linux 启动脚本 +├── start-macos.sh # macOS 启动脚本 +└── README.md # 使用说明 +``` + +## 使用方法 + +### Windows +```cmd +# 方法1: 双击运行 +start-windows.bat + +# 方法2: 直接运行 +drplayer-server-win-x64.exe +``` + +### Linux +```bash +# 添加执行权限 +chmod +x drplayer-server-linux-x64 +chmod +x start-linux.sh + +# 运行 +./start-linux.sh +# 或 +./drplayer-server-linux-x64 +``` + +### macOS +```bash +# 添加执行权限 +chmod +x drplayer-server-macos-x64 +chmod +x start-macos.sh + +# 运行 +./start-macos.sh +# 或 +./drplayer-server-macos-x64 +``` + +## 访问地址 + +服务器启动后,访问地址为: +- **主页**: http://localhost:9978/ +- **应用**: http://localhost:9978/apps/drplayer/ +- **健康检查**: http://localhost:9978/health + +如果端口 9978 被占用,服务器会自动尝试下一个可用端口。 + +## 注意事项 + +### 1. 文件依赖 +- 二进制文件包含了所有 Node.js 依赖 +- 静态文件(HTML、CSS、JS)需要在运行时存在 +- 首次运行会在当前目录创建 `apps` 文件夹 + +### 2. 权限要求 +- Linux/macOS 需要执行权限 +- Windows 可能需要管理员权限(取决于安装位置) + +### 3. 防火墙设置 +- 确保防火墙允许对应端口的访问 +- 默认绑定到 `0.0.0.0`,支持外部访问 + +### 4. 性能优化 +- 使用 Brotli 压缩减小文件大小 +- 二进制文件启动速度比 Node.js 脚本稍慢 + +## 故障排除 + +### 1. 端口占用 +``` +🔍 正在查找可用端口,起始端口: 9978 +端口 9978 已被占用,尝试下一个端口... +端口 9979 已被占用,尝试下一个端口... +✅ 找到可用端口: 9980 +``` + +### 2. 文件缺失 +``` +⚠️ dist目录不存在,跳过文件复制 +📦 pkg环境中,请确保静态文件已正确打包 +``` + +### 3. 构建失败 +``` +⚠️ 构建命令执行失败,可能是在打包环境中运行 +📦 跳过构建步骤,使用预构建的文件 +``` + +## 开发建议 + +### 1. 预构建资源 +在打包前确保运行: +```bash +pnpm build:fastify +``` + +### 2. 测试环境 +在不同平台测试二进制文件: +```bash +# 测试启动 +./drplayer-server-linux-x64 + +# 测试健康检查 +curl http://localhost:9978/health +``` + +### 3. 自动化部署 +可以将打包脚本集成到 CI/CD 流程中: +```yaml +# GitHub Actions 示例 +- name: Build Binary + run: | + npm install -g pkg + pnpm build:fastify + pnpm run pkg:all +``` + +## 版本信息 + +- **Node.js 版本**: 18 +- **PKG 目标**: node18-* +- **压缩算法**: Brotli +- **默认端口**: 9978 + +## 更新日志 + +### v1.0.0 +- 初始版本 +- 支持跨平台打包 +- 智能端口检测 +- PKG 环境优化 \ No newline at end of file diff --git a/dashboard/docs/DEPLOYMENT.md b/dashboard/docs/DEPLOYMENT.md new file mode 100644 index 0000000..f51d060 --- /dev/null +++ b/dashboard/docs/DEPLOYMENT.md @@ -0,0 +1,231 @@ +# DrPlayer Dashboard 部署指南 + +本文档提供了 DrPlayer Dashboard 的完整部署指南,解决了子目录部署和SPA路由刷新的问题。 + +## 构建配置 + +### 1. 环境变量配置 + +在项目根目录创建 `.env.production` 文件: + +```bash +# 生产环境配置 +NODE_ENV=production + +# 如果部署到子目录,设置基础路径 +# 例如:部署到 /apps/ 目录下 +VITE_BASE_PATH=/apps/ + +# 如果部署到根目录,使用相对路径 +# VITE_BASE_PATH=./ +``` + +### 2. 构建命令 + +```bash +# 安装依赖 +npm install + +# 构建生产版本 +npm run build + +# 构建后的文件在 dist 目录中 +``` + +## 部署配置 + +### Nginx 配置 + +#### 1. 根目录部署 + +如果部署到网站根目录(如 `https://example.com/`): + +```nginx +server { + listen 80; + server_name example.com; + root /var/www/drplayer/dist; + index index.html; + + # 处理静态资源 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # 处理 SPA 路由 + location / { + try_files $uri $uri/ /index.html; + } + + # 安全配置 + location ~ /\. { + deny all; + } +} +``` + +#### 2. 子目录部署 + +如果部署到子目录(如 `https://example.com/apps/`): + +```nginx +server { + listen 80; + server_name example.com; + + # 子目录配置 + location /apps/ { + alias /var/www/drplayer/dist/; + index index.html; + + # 处理静态资源 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # 处理 SPA 路由 - 关键配置 + try_files $uri $uri/ /apps/index.html; + } + + # 安全配置 + location ~ /\. { + deny all; + } +} +``` + +#### 3. 使用 location 块的高级配置 + +```nginx +server { + listen 80; + server_name example.com; + + location /apps { + alias /var/www/drplayer/dist; + + # 重写规则处理子目录 + location ~ ^/apps/(.*)$ { + try_files /$1 /$1/ /index.html; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } +} +``` + +### Apache 配置 + +如果使用 Apache 服务器,在 `dist` 目录创建 `.htaccess` 文件: + +```apache +# 根目录部署 +RewriteEngine On +RewriteBase / + +# 处理静态资源 +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.html [L] + +# 子目录部署(例如 /apps/) +# RewriteEngine On +# RewriteBase /apps/ +# RewriteCond %{REQUEST_FILENAME} !-f +# RewriteCond %{REQUEST_FILENAME} !-d +# RewriteRule . /apps/index.html [L] +``` + +## 部署步骤 + +### 1. 准备构建 + +```bash +# 1. 设置环境变量(根据部署目录) +echo "VITE_BASE_PATH=/apps/" > .env.production + +# 2. 构建项目 +npm run build +``` + +### 2. 上传文件 + +```bash +# 将 dist 目录内容上传到服务器 +scp -r dist/* user@server:/var/www/drplayer/dist/ +``` + +### 3. 配置服务器 + +```bash +# 1. 配置 nginx +sudo nano /etc/nginx/sites-available/drplayer +sudo ln -s /etc/nginx/sites-available/drplayer /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx + +# 2. 设置文件权限 +sudo chown -R www-data:www-data /var/www/drplayer/ +sudo chmod -R 755 /var/www/drplayer/ +``` + +## 常见问题解决 + +### 1. 资源加载 404 + +**问题**:部署到子目录后,CSS/JS 文件加载失败 + +**解决**: +- 确保 `.env.production` 中设置了正确的 `VITE_BASE_PATH` +- 检查 nginx 配置中的 `alias` 路径是否正确 + +### 2. 路由刷新 404 + +**问题**:直接访问路由地址或刷新页面时出现 404 + +**解决**: +- 确保 nginx 配置了正确的 `try_files` 规则 +- 检查 `try_files` 中的 fallback 路径是否正确 + +### 3. 子目录路由问题 + +**问题**:子目录部署时路由跳转不正确 + +**解决**: +- 确保 Vue Router 的 base 路径配置正确 +- 检查环境变量 `VITE_BASE_PATH` 是否正确设置 + +## 验证部署 + +部署完成后,验证以下功能: + +1. **静态资源加载**:检查浏览器开发者工具,确保所有 CSS/JS 文件正常加载 +2. **路由导航**:点击菜单项,确保路由跳转正常 +3. **页面刷新**:在任意页面刷新,确保不出现 404 错误 +4. **直接访问**:直接在地址栏输入路由地址,确保能正常访问 + +## 性能优化建议 + +1. **启用 Gzip 压缩** +2. **设置静态资源缓存** +3. **使用 CDN 加速** +4. **启用 HTTP/2** + +```nginx +# Gzip 压缩 +gzip on; +gzip_vary on; +gzip_min_length 1024; +gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + +# HTTP/2 +listen 443 ssl http2; +``` \ No newline at end of file diff --git a/dashboard/docs/FASTIFY_DEPLOYMENT.md b/dashboard/docs/FASTIFY_DEPLOYMENT.md new file mode 100644 index 0000000..efc7a71 --- /dev/null +++ b/dashboard/docs/FASTIFY_DEPLOYMENT.md @@ -0,0 +1,175 @@ +# Vue SPA + Fastify 部署指南 + +## 问题说明 + +Vue单页应用使用`createWebHistory`路由模式时,刷新页面会出现404错误。这是因为: + +1. 用户访问 `/apps/drplayer/settings` +2. 浏览器向服务器请求 `/apps/drplayer/settings` 文件 +3. 服务器找不到该文件,返回404 +4. 需要让服务器将所有SPA路由请求都返回到 `index.html` + +## 解决方案 + +通过Fastify提供静态文件服务 + SPA路由回退机制,完全可以解决刷新404问题。 + +## 配置步骤 + +### 1. 构建Vue应用 + +```bash +# 方法1:使用构建脚本(推荐) +node build-for-fastify.js + +# 方法2:手动设置环境变量 +set VITE_BASE_PATH=/apps/drplayer/ +pnpm build +``` + +### 2. 复制构建文件 + +将 `dist/` 目录的所有内容复制到您的后端 `apps/drplayer/` 目录: + +``` +your-backend/ +├── apps/ +│ └── drplayer/ +│ ├── index.html +│ ├── assets/ +│ │ ├── index-xxx.js +│ │ └── index-xxx.css +│ └── ... +└── server.js +``` + +### 3. 配置Fastify服务器 + +#### 方法1:使用提供的路由模块(推荐) + +```javascript +import { addSPARoutes } from './fastify-spa-routes.js'; +import fastifyStatic from '@fastify/static'; + +// 您现有的静态文件配置 +await fastify.register(fastifyStatic, { + root: options.appsDir, + prefix: '/apps/', + decorateReply: false, +}); + +// 添加SPA路由支持 +await fastify.register(addSPARoutes, { + appsDir: options.appsDir +}); +``` + +#### 方法2:直接在现有代码中添加 + +```javascript +import path from 'path'; +import fs from 'fs'; + +// 在您现有的Fastify应用中添加这些路由 +fastify.get('/apps/drplayer/*', async (request, reply) => { + const requestedPath = request.params['*']; + const fullPath = path.join(options.appsDir, 'drplayer', requestedPath); + + try { + await fs.promises.access(fullPath); + return reply.callNotFound(); // 让静态文件服务处理 + } catch (error) { + // 返回index.html让Vue Router处理 + const indexPath = path.join(options.appsDir, 'drplayer', 'index.html'); + const indexContent = await fs.promises.readFile(indexPath, 'utf8'); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); + } +}); + +// 处理根路径 +fastify.get('/apps/drplayer', async (request, reply) => { + return reply.redirect(301, '/apps/drplayer/'); +}); + +fastify.get('/apps/drplayer/', async (request, reply) => { + const indexPath = path.join(options.appsDir, 'drplayer', 'index.html'); + const indexContent = await fs.promises.readFile(indexPath, 'utf8'); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); +}); +``` + +## 工作原理 + +1. **静态文件服务**:`@fastify/static` 处理所有存在的静态文件(JS、CSS、图片等) +2. **路由回退**:当请求的文件不存在时,返回 `index.html` +3. **Vue Router接管**:`index.html` 加载后,Vue Router根据URL显示对应组件 + +## 路由优先级 + +``` +请求: /apps/drplayer/assets/index-xxx.js +↓ +静态文件存在 → 直接返回文件 + +请求: /apps/drplayer/settings +↓ +静态文件不存在 → 返回 index.html → Vue Router处理 +``` + +## 缓存策略 + +- **HTML文件**:不缓存(`no-cache`),确保路由更新 +- **静态资源**:长期缓存(1年),提高性能 + +## 测试验证 + +1. 启动Fastify服务器 +2. 访问 `http://localhost:5757/apps/drplayer/` +3. 导航到不同页面(如设置页面) +4. 刷新页面,确认不出现404错误 +5. 检查浏览器开发者工具,确认静态资源正常加载 + +## 常见问题 + +### Q: 刷新后页面空白? +A: 检查 `VITE_BASE_PATH` 是否正确设置为 `/apps/drplayer/` + +### Q: 静态资源404? +A: 确认构建时的base路径与Fastify的prefix匹配 + +### Q: API请求失败? +A: 确保API路由在SPA路由之前注册,避免被SPA回退拦截 + +## 性能优化 + +1. **启用Gzip压缩**: +```javascript +import fastifyCompress from '@fastify/compress'; +await fastify.register(fastifyCompress); +``` + +2. **设置静态资源缓存**: +```javascript +await fastify.register(fastifyStatic, { + // ... 其他配置 + setHeaders: (res, path) => { + if (path.endsWith('.html')) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + } else if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) { + res.setHeader('Cache-Control', 'public, max-age=31536000'); + } + } +}); +``` + +## 总结 + +✅ **可以解决404问题**:通过Fastify的路由回退机制 +✅ **性能良好**:静态文件直接服务,只有路由请求才回退 +✅ **配置简单**:只需添加几个路由处理器 +✅ **开发友好**:支持热重载和开发服务器 \ No newline at end of file diff --git a/dashboard/docs/NGINX_DEPLOYMENT.md b/dashboard/docs/NGINX_DEPLOYMENT.md new file mode 100644 index 0000000..b0907d7 --- /dev/null +++ b/dashboard/docs/NGINX_DEPLOYMENT.md @@ -0,0 +1,194 @@ +# Vue SPA Nginx部署指南 + +本文档说明如何解决Vue单页应用(SPA)在静态部署时的404问题,并提供完整的Nginx配置方案。 + +## 问题原因 + +Vue Router使用`createWebHistory`模式时,路由是通过浏览器的History API实现的。当用户直接访问或刷新非根路径页面时(如`/settings`),服务器会尝试查找对应的物理文件,但这些文件并不存在,因此返回404错误。 + +## 解决方案 + +通过Nginx配置`try_files`指令,将所有不匹配静态文件的请求重定向到`index.html`,让Vue Router接管路由处理。 + +## 部署方式 + +### 方式一:根目录部署 + +适用于将DrPlayer部署在域名根目录的情况,如:`https://example.com/` + +#### 1. 构建应用 +```bash +# 使用根目录打包脚本 +pnpm build:root +``` + +#### 2. 部署文件 +将`dist`目录中的所有文件复制到服务器的网站根目录: +```bash +# 示例路径 +/var/www/html/drplayer/ +``` + +#### 3. Nginx配置 +使用提供的`nginx-root.conf`配置文件: +```bash +# 复制配置文件 +sudo cp nginx-root.conf /etc/nginx/sites-available/drplayer +sudo ln -s /etc/nginx/sites-available/drplayer /etc/nginx/sites-enabled/ + +# 或者直接编辑默认配置 +sudo nano /etc/nginx/sites-available/default +``` + +### 方式二:子目录部署 + +适用于将DrPlayer部署在域名子目录的情况,如:`https://example.com/apps/drplayer/` + +#### 1. 构建应用 +```bash +# 使用子目录打包脚本 +pnpm build:apps +``` + +#### 2. 部署文件 +将`dist`目录中的所有文件复制到服务器的指定子目录: +```bash +# 示例路径 +/var/www/html/drplayer/ +``` + +#### 3. Nginx配置 +使用提供的`nginx-subdir.conf`配置文件: +```bash +# 复制配置文件 +sudo cp nginx-subdir.conf /etc/nginx/sites-available/drplayer-subdir +sudo ln -s /etc/nginx/sites-available/drplayer-subdir /etc/nginx/sites-enabled/ +``` + +## 配置文件说明 + +### 核心配置项 + +1. **try_files指令** + ```nginx + try_files $uri $uri/ /index.html; + ``` + 这是解决SPA路由404问题的关键配置。 + +2. **静态资源缓存** + ```nginx + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + ``` + +3. **Gzip压缩** + ```nginx + gzip on; + gzip_types text/plain text/css application/json application/javascript; + ``` + +4. **安全头设置** + ```nginx + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + ``` + +### 路径配置 + +- **根目录部署**:修改`root`指令指向您的实际部署路径 +- **子目录部署**:修改`alias`指令指向您的实际部署路径 + +## 部署步骤 + +### 1. 准备服务器环境 +```bash +# 安装Nginx(Ubuntu/Debian) +sudo apt update +sudo apt install nginx + +# 安装Nginx(CentOS/RHEL) +sudo yum install nginx +# 或者 +sudo dnf install nginx +``` + +### 2. 配置Nginx +```bash +# 备份原配置 +sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.backup + +# 应用新配置 +sudo cp nginx-root.conf /etc/nginx/sites-available/drplayer +sudo ln -s /etc/nginx/sites-available/drplayer /etc/nginx/sites-enabled/ + +# 测试配置 +sudo nginx -t + +# 重启Nginx +sudo systemctl restart nginx +``` + +### 3. 部署应用文件 +```bash +# 创建部署目录 +sudo mkdir -p /var/www/html/drplayer + +# 复制构建文件 +sudo cp -r dist/* /var/www/html/drplayer/ + +# 设置权限 +sudo chown -R www-data:www-data /var/www/html/drplayer +sudo chmod -R 755 /var/www/html/drplayer +``` + +### 4. 验证部署 +1. 访问首页:`http://your-domain/` +2. 直接访问子页面:`http://your-domain/settings` +3. 刷新页面确认不会出现404错误 + +## 常见问题 + +### 1. 刷新页面仍然404 +- 检查Nginx配置中的`try_files`指令是否正确 +- 确认`root`或`alias`路径是否正确 +- 检查文件权限是否正确 + +### 2. 静态资源加载失败 +- 检查`base`路径配置是否与部署路径匹配 +- 确认静态资源文件是否存在 +- 检查Nginx静态文件配置 + +### 3. API请求失败 +- 配置API代理(如果后端API在不同端口) +- 检查CORS设置 +- 确认API路径配置 + +## 性能优化建议 + +1. **启用Gzip压缩**:减少传输文件大小 +2. **设置静态资源缓存**:提高加载速度 +3. **使用CDN**:加速静态资源访问 +4. **启用HTTP/2**:提高传输效率 +5. **配置SSL证书**:启用HTTPS + +## 安全建议 + +1. **隐藏Nginx版本信息** +2. **设置安全响应头** +3. **禁止访问敏感文件** +4. **配置防火墙规则** +5. **定期更新系统和软件** + +## 监控和日志 + +```nginx +# 访问日志 +access_log /var/log/nginx/drplayer_access.log; + +# 错误日志 +error_log /var/log/nginx/drplayer_error.log; +``` + +通过以上配置,您的Vue SPA应用将能够正确处理所有路由,解决刷新页面404的问题。 \ No newline at end of file diff --git a/dashboard/docs/OPTIMIZATION_REPORT.md b/dashboard/docs/OPTIMIZATION_REPORT.md new file mode 100644 index 0000000..453e9ce --- /dev/null +++ b/dashboard/docs/OPTIMIZATION_REPORT.md @@ -0,0 +1,96 @@ +# DrPlayer PKG 打包优化报告 + +## 📊 优化结果总览 + +| 指标 | 原始版本 | 优化版本 | 改善幅度 | +|------|----------|----------|----------| +| 文件大小 | 45.70 MB | 38.85 MB | **-15.0%** | +| 构建脚本 | `build:binary` | `build:binary:optimized` | 新增优化版本 | +| 压缩技术 | 无 | Brotli + UPX检测 | 智能压缩 | + +## 🔧 实施的优化措施 + +### 1. PKG 内置优化选项 +- **Brotli 压缩**: 启用 `--compress Brotli` 选项 +- **公共包优化**: 使用 `--public-packages "*"` 减少重复打包 +- **公共模式**: 启用 `--public` 加速打包过程 +- **内存限制**: 设置 `--options "max-old-space-size=512"` 优化内存使用 + +### 2. UPX 压缩集成 +- **智能检测**: 自动检测 UPX 压缩后的兼容性 +- **自动回滚**: 如果压缩后无法运行,自动恢复原文件 +- **备份机制**: 保留原始文件备份以确保安全 +- **兼容性优先**: 发现 pkg 生成的二进制文件与 UPX 存在兼容性问题 + +## 📁 新增文件 + +### 构建脚本 +- - 优化版构建脚本 +- 新增 npm 脚本: `npm run build:binary:optimized` + +### 文档 +- - UPX 压缩使用指南 +- - 本优化报告 + +## 🚀 使用方法 + +### 优化构建 +```bash +# 使用优化版本构建 +npm run build:binary:optimized + +# 或直接运行脚本 +node build-binary-optimized.js +``` + +### 标准构建(对比用) +```bash +# 原始版本构建 +npm run build:binary +``` + +## ⚠️ 重要发现 + +### UPX 兼容性问题 +经过测试发现,**pkg 生成的二进制文件与 UPX 压缩存在兼容性问题**: + +1. **问题现象**: UPX 压缩后的二进制文件无法正常启动 +2. **错误信息**: `Pkg: Error reading from file.` +3. **根本原因**: pkg 使用特殊的文件格式和内部偏移量,UPX 压缩会破坏这些结构 +4. **解决方案**: 实现了智能检测和自动回滚机制 + +### 最佳实践建议 +1. **优先使用 pkg 内置优化**: Brotli 压缩等选项安全可靠 +2. **谨慎使用 UPX**: 仅在确认兼容性后使用 +3. **保留备份**: 始终保留原始文件备份 +4. **测试验证**: 压缩后必须进行功能测试 + +## 📈 性能影响 + +### 文件大小 +- **原始**: 45.70 MB +- **优化**: 38.85 MB +- **减少**: 6.85 MB (15.0%) + +### 启动性能 +- **功能完整性**: ✅ 完全保持 +- **启动时间**: 📈 略有改善(文件更小) +- **运行稳定性**: ✅ 无影响 + +## 🔮 未来优化方向 + +1. **前端代码分割**: 减少 JavaScript bundle 大小 +2. **依赖优化**: 移除不必要的依赖包 +3. **资源压缩**: 优化图片和静态资源 +4. **Tree Shaking**: 更精确的无用代码消除 + +## 📞 技术支持 + +如遇到问题,请参考: +1. - 详细的 UPX 使用指南 +2. 构建脚本中的错误处理和日志输出 +3. 备份文件恢复机制 + +--- +*报告生成时间: $(date)* +*优化版本: v1.0* \ No newline at end of file diff --git a/dashboard/README.md b/dashboard/docs/README.md similarity index 100% rename from dashboard/README.md rename to dashboard/docs/README.md diff --git "a/dashboard/docs/T4-Action\344\272\244\344\272\222\345\212\237\350\203\275\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/dashboard/docs/T4-Action\344\272\244\344\272\222\345\212\237\350\203\275\345\274\200\345\217\221\346\214\207\345\215\227.md" new file mode 100644 index 0000000..b27a2e3 --- /dev/null +++ "b/dashboard/docs/T4-Action\344\272\244\344\272\222\345\212\237\350\203\275\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -0,0 +1,482 @@ +# T4 Action交互功能开发指南 + +## 概述 + +T4 Action交互功能是DrPlayer中用于实现用户与站源接口间动态交互的核心机制。通过返回特定格式的JSON配置,可以动态生成各种类型的输入界面、弹窗和交互组件,实现复杂的用户交互流程。 + +## 核心概念 + +### 1. 交互动作(Action) +影图APP与站源接口间交互的动作指令,用户在APP主动发出动作请求,接口根据指令返回数据或返回构建信息输入窗口的配置JSON,具有连续交互的机制。 + +### 2. 动作类型分类 + +#### 静态动作 +- 用户通过视频分类列表主动发起交互的起点动作 +- 动作指令以分类列表的视频JSON数据为基础,属于静态数据 +- 所有交互动作的起始都是静态动作 + +#### 动态动作 +- 静态动作构建的信息输入窗口提交action数据后,接口如果再次需要用户输入数据,可以返回新的动作配置JSON数据 +- 此数据是交互过程中动态生成的,属于动态动作 + +### 3. 数据结构 + +#### 视频VOD结构(静态动作入口) +```json +{ + "vod_id": "动作指令结构...", + "vod_name": "显示名称", + "vod_tag": "action", + "vod_pic": "图片URL(可选)", + "vod_remarks": "备注信息(可选)" +} +``` + +#### 动态动作返回结构 +```json +{ + "action": { + // 动作指令结构... + }, + "toast": "Toast显示消息" +} +``` + +## 动作类型详解 + +### 1. 基础动作 +简单的动作指令字符串(非JSON结构),用户点击时无信息输入窗口,直接发送指令。 + +**示例:** +```json +{ + "vod_id": "hello world", + "vod_name": "基础动作", + "vod_tag": "action" +} +``` + +### 2. 单项输入(input) +要求用户输入一个字段的动作。 + +**核心字段:** +- `actionId`: 识别动作的路由ID(必须) +- `type`: "input" +- `id`: 输入项目id +- `title`: 输入窗口标题 +- `tip`: 输入提示(必须) +- `value`: 输入初始值 +- `msg`: 窗口文本说明 +- `width`: 宽度 +- `button`: 按键数量(0-无按键,1-取消,2-确定/取消,3-确定/取消/重置) +- `selectData`: 预定义选项,格式:"1:=选项一,2:=选项二" +- `imageUrl`: 窗口显示图片的URL +- `imageHeight`: 图片高度 +- `imageClickCoord`: 是否检测图片的点击坐标输入 +- `qrcode`: 生成二维码的URL +- `qrcodeSize`: 二维码的大小 +- `timeout`: 超时时间(秒) +- `keep`: 输入确认后,窗口是否保持 +- `initAction`: 窗口弹出时自动发送的初始化动作指令 +- `initValue`: 窗口弹出时自动发送的初始化指令值 +- `cancelAction`: 按窗口的取消键时发送的取消动作指令 +- `cancelValue`: 按窗口的取消键时发送的取消动作指令值 + +**示例:** +```json +{ + "actionId": "玩偶域名", + "id": "domain", + "type": "input", + "width": 450, + "title": "玩偶域名", + "tip": "请输入玩偶域名", + "value": "", + "msg": "选择或输入使用的域名", + "selectData": "1:=https://www.wogg.net/,2:=https://wogg.xxooo.cf/" +} +``` + +### 3. 多行编辑(edit) +要求用户在一个多行编辑区输入单个字段内容的动作。 + +**示例:** +```json +{ + "actionId": "多行编辑", + "type": "edit", + "id": "alitoken", + "title": "阿里云盘Token", + "msg": "阿里云盘32位的Token", + "tip": "请输入阿里云盘32位的Token", + "value": "", + "width": 640, + "height": 400 +} +``` + +### 4. 多项输入(multiInput) +要求用户输入多个字段(5个以内)的动作。建议使用"增强多项输入"。 + +### 5. 增强多项输入(multiInputX) +要求用户输入多个字段(不限制个数)的动作,功能最强大的输入类型。 + +**核心字段:** +- `type`: "multiInputX" +- `canceledOnTouchOutside`: 弹出窗口是否允许触摸窗口外时取消窗口 +- `height`: 高度(负数表示从底部计算) +- `bottom`: 底部对齐和底边距 +- `dimAmount`: 设置窗口背景暗化效果(0.0-1.0) +- `input`: 输入项目定义JSON数组 + +**输入项目字段:** +- `id`: 项目id +- `name`: 项目名称 +- `tip`: 项目输入提示 +- `value`: 项目初始值 +- `selectData`: 项目输入预定义选项 + - `[folder]`: 选择文件夹 + - `[file]`: 选择文件 + - `[calendar]`: 选择日期 + - `[image]`: 选择图像文件转为BASE64 +- `quickSelect`: 是否能快速选择 +- `onlyQuickSelect`: 是否只快速选择,隐藏输入框等 +- `selectWidth`: 选择窗的宽度 +- `multiSelect`: 是否多选 +- `selectColumn`: 选择窗的列数 +- `inputType`: 项目输入类型(0-只读但可通过选项输入,129-密码输入) +- `multiLine`: 项目输入框的行数(多行编辑) +- `validation`: 提交时项目输入值校验正则表达式 +- `help`: 项目输入的帮助说明,支持简易HTML + +**示例:** +```json +{ + "actionId": "多项输入", + "type": "multiInputX", + "canceledOnTouchOutside": true, + "title": "Action多项输入(multiInputX)", + "width": 716, + "height": -300, + "bottom": 1, + "dimAmount": 0.3, + "button": 3, + "input": [ + { + "id": "item1", + "name": "文件夹路径(文件夹选择)", + "tip": "请输入文件夹路径", + "value": "", + "selectData": "[folder]", + "inputType": 0 + }, + { + "id": "item2", + "name": "多项选择", + "tip": "请输入多项内容,以","分隔", + "value": "", + "selectData": "[请选择字母]a,b,c,d,e,f,g", + "selectWidth": 640, + "multiSelect": true, + "selectColumn": 4, + "inputType": 0 + } + ] +} +``` + +### 6. 单项选择(menu) +要求用户在列表中选择一个项目的动作。 + +**核心字段:** +- `type`: "menu" +- `column`: 单项选择窗口的列数 +- `option`: 选项定义JSON数组 +- `selectedIndex`: 默认选中的索引 + +**选项格式:** +- 对象格式:`{"name": "菜单1", "action": "menu1"}` +- 字符串格式:`"菜单3$menu3"` + +**示例:** +```json +{ + "actionId": "单选菜单", + "type": "menu", + "title": "Action菜单", + "width": 300, + "column": 2, + "option": [ + {"name": "菜单1", "action": "menu1"}, + {"name": "菜单2", "action": "menu2"}, + "菜单3$menu3", + "菜单4$menu4" + ], + "selectedIndex": 3 +} +``` + +### 7. 多项选择(select) +要求用户在列表中选择多个项目的动作。 + +**示例:** +```json +{ + "actionId": "多选菜单", + "type": "select", + "title": "Action多选菜单", + "width": 480, + "column": 2, + "option": [ + {"name": "选项1", "action": "menu1", "selected": true}, + {"name": "选项2", "action": "menu2"}, + {"name": "选项3", "action": "menu3", "selected": true} + ] +} +``` + +### 8. 消息弹窗(msgbox) +弹出窗口显示消息。 + +**核心字段:** +- `type`: "msgbox" +- `msg`: 文本消息内容 +- `htmlMsg`: 简单html消息内容 +- `imageUrl`: 图片URL + +**示例:** +```json +{ + "actionId": "消息弹窗", + "type": "msgbox", + "title": "消息弹窗", + "htmlMsg": "这是一个支持 简单HTML语法 内容的弹窗", + "imageUrl": "https://pic.imgdb.cn/item/667ce9f4d9c307b7e9f9d052.webp" +} +``` + +### 9. WebView(webview) +嵌入网页视图。 + +**示例:** +```json +{ + "actionId": "WEBVIEW", + "type": "webview", + "height": -260, + "textZoom": 70, + "url": "http://127.0.0.1:9978/" +} +``` + +### 10. 帮助页面(help) +显示帮助信息。 + +**示例:** +```json +{ + "actionId": "help", + "type": "help", + "title": "使用帮助", + "data": { + "使用帮助": "暂未收录内容" + } +} +``` + +## 专项动作 + +专项动作为动态动作,接口让APP执行一些特定的行为动作。actionId值为行为特定的标识。 + +### 1. 源内搜索(__self_search__) +```json +{ + "actionId": "__self_search__", + "skey": "目标源key(可选)", + "name": "搜索分类名称", + "tid": "使用分类ID传递的搜索值", + "flag": "列表视图参数", + "folder": "查询1$搜索词$0-0-S#查询2$搜索词$3#查询3$搜索词$5" +} +``` + +### 2. 详情页跳转(__detail__) +```json +{ + "actionId": "__detail__", + "skey": "目标源key", + "ids": "传递给详情页的视频ids" +} +``` + +### 3. KTV播放(__ktvplayer__) +```json +{ + "actionId": "__ktvplayer__", + "name": "歌名", + "id": "歌曲的直链" +} +``` + +### 4. 刷新列表(__refresh_list__) +```json +{ + "actionId": "__refresh_list__" +} +``` + +### 5. 复制到剪贴板(__copy__) +```json +{ + "actionId": "__copy__", + "content": "复制的内容" +} +``` + +### 6. 保持窗口(__keep__) +```json +{ + "actionId": "__keep__", + "msg": "更新窗口里的文本消息内容", + "reset": true +} +``` + +## 接口实现 + +### action函数签名 +```javascript +async function action(action, value) { + // action: 动作指令 + // value: 动作指令值 + // 返回: 结果消息或新的动作指令数据(动态动作) +} +``` + +### 实现示例 + +#### 1. 基础动作处理 +```javascript +if (action == 'set-cookie') { + return JSON.stringify({ + action: { + actionId: 'quarkCookieConfig', + id: 'cookie', + type: 'input', + title: '夸克Cookie', + tip: '请输入夸克的Cookie', + value: '原值', + msg: '此弹窗是动态设置的参数,可用于动态返回原设置值等场景' + } + }); +} +``` + +#### 2. 输入数据处理 +```javascript +if (action == 'quarkCookieConfig' && value) { + try { + const obj = JSON.parse(value); + const val = obj.cookie; + return "我收到了:" + value; + } catch (e) { + return '发生错误:' + e; + } +} +``` + +#### 3. 连续对话处理 +```javascript +if (action == '连续对话') { + let content = JSON.parse(value); + if (content.talk.indexOf('http') == 0) { + return JSON.stringify({ + action: { + actionId: '__detail__', + skey: 'push_agent', + ids: content.talk, + }, + toast: '你要去看视频了' + }); + } + return JSON.stringify({ + action: { + actionId: '__keep__', + msg: '回音:' + content.talk, + reset: true + }, + toast: '你有新的消息' + }); +} +``` + +#### 4. 扫码功能实现 +```javascript +if (action == '夸克扫码') { + if (rule.quarkScanCheck) { + return '请等待上个扫码任务完成'; + } + + let requestId = generateUUID(); + // 获取扫码token的API调用... + + return JSON.stringify({ + action: { + actionId: 'quarkScanCookie', + id: 'quarkScanCookie', + canceledOnTouchOutside: false, + type: 'input', + title: '夸克扫码Cookie', + msg: '请使用夸克APP扫码登录获取', + width: 500, + button: 1, + timeout: 20, + qrcode: qrcodeUrl, + qrcodeSize: '400', + initAction: 'quarkScanCheck', + initValue: requestId, + cancelAction: 'quarkScanCancel', + cancelValue: requestId, + } + }); +} +``` + +## 开发注意事项 + +### 1. 数据格式 +- 所有JSON动作都需要使用`JSON.stringify()`转换为字符串 +- 动态动作返回时需要包装在`action`字段中 +- 可选择性添加`toast`字段显示提示消息 + +### 2. 错误处理 +- 使用try-catch处理JSON解析错误 +- 提供友好的错误提示信息 +- 考虑超时和取消操作的处理 + +### 3. 状态管理 +- 对于需要状态的操作(如扫码),使用全局变量管理状态 +- 及时清理状态,避免内存泄漏 +- 处理并发操作的冲突 + +### 4. 用户体验 +- 合理设置窗口大小和位置 +- 提供清晰的提示信息和帮助文档 +- 支持取消操作和超时处理 +- 使用适当的图标和图片增强视觉效果 + +### 5. 安全考虑 +- 验证用户输入数据 +- 使用正则表达式验证格式 +- 避免执行不安全的操作 + +## 最佳实践 + +1. **模块化设计**:将不同类型的动作处理分离到不同的函数中 +2. **配置驱动**:使用配置文件管理常用的选项和参数 +3. **错误恢复**:提供重试机制和错误恢复选项 +4. **性能优化**:避免在动作处理中执行耗时操作 +5. **文档完善**:为每个动作提供清晰的使用说明 + +通过以上指南,开发者可以充分利用T4 Action交互功能,创建丰富的用户交互体验。 \ No newline at end of file diff --git a/dashboard/docs/UPX_COMPRESSION_GUIDE.md b/dashboard/docs/UPX_COMPRESSION_GUIDE.md new file mode 100644 index 0000000..a7a5854 --- /dev/null +++ b/dashboard/docs/UPX_COMPRESSION_GUIDE.md @@ -0,0 +1,188 @@ +# UPX压缩优化指南 + +## 概述 + +UPX (Ultimate Packer for eXecutables) 是一个高性能的可执行文件压缩工具,可以将pkg生成的二进制文件体积减少50%-70%。 + +## 当前优化效果 + +- **原始pkg打包**: ~45.70 MB +- **pkg优化选项**: 预计减少10-20% +- **UPX压缩**: 预计额外减少50-70% +- **最终预期大小**: ~10-20 MB + +## UPX安装 + +### Windows + +1. **下载UPX** + - 访问 https://upx.github.io/ + - 下载最新版本的Windows版本 + - 解压到任意目录(如 `C:\tools\upx`) + +2. **添加到PATH环境变量** + ```cmd + # 方法1: 通过系统设置 + # 控制面板 > 系统 > 高级系统设置 > 环境变量 + # 在PATH中添加UPX解压目录 + + # 方法2: 通过PowerShell临时添加 + $env:PATH += ";C:\tools\upx" + ``` + +3. **验证安装** + ```cmd + upx --version + ``` + +### 使用Chocolatey安装(推荐) + +```powershell +# 安装Chocolatey(如果未安装) +Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + +# 安装UPX +choco install upx +``` + +### 使用Scoop安装 + +```powershell +# 安装Scoop(如果未安装) +iwr -useb get.scoop.sh | iex + +# 安装UPX +scoop install upx +``` + +## 使用方法 + +### 1. 基础优化构建 + +```bash +# 使用pkg内置优化选项 +pnpm build:binary:optimized +``` + +### 2. 手动UPX压缩 + +```bash +# 先进行常规构建 +pnpm build:binary + +# 然后手动压缩 +upx --best dist-binary/drplayer-server-win.exe +``` + +### 3. 不同压缩级别 + +```bash +# 快速压缩(压缩率较低,速度快) +upx --fast dist-binary/drplayer-server-win.exe + +# 更好的压缩(平衡压缩率和速度) +upx --better dist-binary/drplayer-server-win.exe + +# 最佳压缩(压缩率最高,速度慢) +upx --best dist-binary/drplayer-server-win.exe + +# 极限压缩(尝试所有方法,非常慢) +upx --best --ultra-brute dist-binary/drplayer-server-win.exe +``` + +## PKG优化选项说明 + +### 1. 压缩选项 +- `--compress Brotli`: 使用Brotli算法压缩文件存储 +- `--compress GZip`: 使用GZip算法压缩(兼容性更好) + +### 2. 公共包选项 +- `--public-packages "*"`: 将所有包标记为公共,减少重复代码 +- `--public`: 加速打包并公开顶级项目源码 + +### 3. 字节码选项 +- `--no-bytecode`: 跳过字节码生成,包含源文件作为纯JS +- 优点:减少体积,加快打包速度 +- 缺点:源码可见(如果这是问题的话) + +### 4. Node.js选项 +- `--options "max-old-space-size=512"`: 限制内存使用 +- `--options "expose-gc"`: 暴露垃圾回收接口 + +## 性能影响 + +### UPX压缩的影响 +- **启动时间**: 增加200-400ms(解压缩时间) +- **运行时性能**: 无影响(解压后在内存中运行) +- **内存使用**: 无额外开销 + +### 优化建议 +1. **开发环境**: 使用常规构建(快速) +2. **测试环境**: 使用pkg优化选项 +3. **生产环境**: 使用完整优化(pkg + UPX) + +## 故障排除 + +### UPX压缩失败 +```bash +# 如果UPX压缩失败,可能的原因: +# 1. 文件被杀毒软件锁定 +# 2. 文件正在使用中 +# 3. 文件格式不支持 + +# 解决方案: +# 1. 临时关闭杀毒软件 +# 2. 确保没有运行该程序 +# 3. 使用 --force 选项强制压缩 +upx --best --force dist-binary/drplayer-server-win.exe +``` + +### 压缩后程序无法运行 +```bash +# 如果压缩后程序无法运行: +# 1. 尝试解压缩 +upx -d dist-binary/drplayer-server-win.exe + +# 2. 使用较低的压缩级别 +upx --fast dist-binary/drplayer-server-win.exe + +# 3. 检查是否与杀毒软件冲突 +``` + +## 自动化脚本配置 + +在 `build-binary-optimized.js` 中可以调整以下配置: + +```javascript +const config = { + pkg: { + compress: 'Brotli', // 'Brotli' | 'GZip' | null + publicPackages: '*', // '*' | 'package1,package2' | null + noBytecode: true, // true | false + options: 'max-old-space-size=512' // Node.js选项 + }, + upx: { + enabled: true, // 是否启用UPX压缩 + level: 'best', // 'fast' | 'better' | 'best' + backup: true // 是否保留原始文件备份 + } +}; +``` + +## 最佳实践 + +1. **渐进式优化**: 先应用pkg优化,再考虑UPX +2. **测试兼容性**: 在目标环境中测试压缩后的程序 +3. **备份原文件**: 始终保留未压缩的版本作为备份 +4. **监控性能**: 测量启动时间影响是否可接受 +5. **CI/CD集成**: 在构建流水线中自动化压缩过程 + +## 预期效果 + +| 优化方案 | 文件大小 | 压缩率 | 启动时间影响 | +|---------|---------|--------|-------------| +| 原始pkg | 45.70 MB | - | 基准 | +| pkg优化 | ~36-40 MB | 10-20% | 无 | +| pkg+UPX | ~10-20 MB | 50-70% | +200-400ms | + +通过这些优化,你的二进制文件大小可以从45MB+减少到10-20MB,大幅降低分发成本和下载时间。 \ No newline at end of file diff --git a/dashboard/docs/apidoc.md b/dashboard/docs/apidoc.md new file mode 100644 index 0000000..809fef7 --- /dev/null +++ b/dashboard/docs/apidoc.md @@ -0,0 +1,194 @@ +# drpyS接口文档 + +本文档基于 Fastify 实现整理,适合国内开发人员快速对接。 + +## 1. 接口概览 + +| 接口名称 | 请求方式 | 地址示例 | +|------------|------------|--------------------| +| 模块数据接口(T4) | GET / POST | `/api/:module` | +| 模块代理接口 | GET | `/proxy/:module/*` | +| 解析接口 | GET | `/parse/:jx` | + +--- + +## 2. 接口详情 + +### 2.1 模块数据接口(T4) + +- **URL**:`/api/:module` +- **请求方式**:`GET` / `POST` +- **鉴权**:需要 `validatePwd` 验证(通过请求参数如?pwd=dzyyds) +- **Content-Type**: + - `application/json` + - `application/x-www-form-urlencoded` + +#### 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|--------|----|-----------------------| +| module | string | 是 | 自定义源文件名称,例如 `腾云驾雾[官]` | + +#### 请求参数(query 或 body) + +以下参数根据业务逻辑不同,**只需传递需要的字段**: + +| 参数名 | 类型 | 说明 | +|---------|--------|----------------------------------------| +| play | string | 播放链接标识 | +| flag | string | 播放标志(配合 `play` 使用) | +| ac | string | 动作类型,可配合 `t`、`ids`、`action` 等字段 | +| t | string | 分类 ID(配合 `ac` 使用) | +| ids | string | 详情 ID(逗号分隔) | +| action | string | 执行动作名称 | +| value | string | 执行动作值 | +| wd | string | 搜索关键字 | +| quick | number | 搜索模式(0 普通,1 快速) | +| refresh | any | 强制刷新初始化 | +| filter | number | 是否开启筛选(默认 1) | +| pg | number | 页码,默认 1 | +| ext | string | Base64 编码的 JSON 筛选参数 | +| extend | string | 扩展参数(直接字符串,根据/config路由对应sites的ext属性传递) | +| do | string | 自定义源适配器,默认ds,可不传 | + +#### 功能分支 + +接口会根据传参进入不同逻辑: + +1. **播放**:`play` 存在 → 调用 `play` 方法 +2. **分类**:`ac`、`t` 存在 → 调用 `cate` (ac=list) +3. **详情**:`ac`、`ids` 存在 → 调用 `detail` (ac=detail) +4. **动作**:`ac`、`action` 存在 → 调用 `action` (ac=action) +5. **搜索**:`wd` 存在 → 调用 `search` +6. **刷新**:`refresh` 存在 → 调用 `init` +7. **默认**:返回 `home` + `homeVod` 数据 + +#### 返回示例 + +```json +{ + "type": "影视", + "class": [ + { + "type_id": "1", + "type_name": "电影" + }, + { + "type_id": "2", + "type_name": "电视剧" + } + ], + "filters": {}, + "list": [ + { + "vod_id": "123", + "vod_name": "示例视频", + "vod_pic": "http://example.com/img.jpg", + "vod_remarks": "更新至第1集" + } + ] +} +``` + +[更多T4接口说明参考](./t4api.md) + +--- + +### 2.2 模块代理接口 + +- **URL**:`/proxy/:module/*` +- **请求方式**:`GET` +- **功能**:转发/代理模块相关资源(可处理 Range 请求,支持流媒体) +- **路径参数**: + | 参数名 | 类型 | 必填 | 说明 | + | ------- | ------ | ---- | ---- | + | module | string | 是 | 模块名称 | + | * | string | 是 | 代理的目标路径 | + +- **查询参数**:与 `/api/:module` 相似,额外支持 `extend` +- **返回值**: + - 可能是二进制文件(图片、视频等) + - 可能是 JSON / 文本 + - 可能 302 重定向到 `/mediaProxy` 流代理地址 + +#### 返回示例(JSON) + +```json +{ + "code": 200, + "msg": "成功", + "data": "内容" +} +``` + +--- + +### 2.3 解析接口 + +- **URL**:`/parse/:jx` +- **请求方式**:`GET` +- **功能**:调用解析脚本解析传入链接(支持跳转、JSON 输出) +- **路径参数**: + | 参数名 | 类型 | 必填 | 说明 | + | ------ | ------ | ---- | ---- | + | jx | string | 是 | 解析脚本名称(对应 `.js` 文件) | + +- **查询参数**: + | 参数名 | 类型 | 必填 | 说明 | + | ------ | ------ | ---- | ---- | + | url | string | 是 | 待解析的链接 | + | extend | string | 否 | 扩展参数 | + +- **返回值**: + - `code`:200 成功,404 失败 + - `msg`:提示信息 + - `url`:解析后的地址 + - `cost`:解析耗时(毫秒) + +#### 返回示例(成功) + +```json +{ + "code": 200, + "url": "http://example.com/play.m3u8", + "msg": "jx1解析成功", + "cost": 123 +} +``` + +#### 返回示例(失败) + +```json +{ + "code": 404, + "url": "http://example.com", + "msg": "jx1解析失败", + "cost": 120 +} +``` + +--- + +## 3. 错误返回格式 + +```json +{ + "error": "错误描述信息" +} +``` + +- 常见错误: + - `Module xxx not found`:模块不存在 + - `解析 xxx not found`:解析脚本不存在 + - `Failed to process module`:模块执行出错 + - `Failed to proxy module`:代理执行出错 + +--- + +## 4. 开发注意事项 + +1. 所有模块和解析脚本必须存在于 `jsDir` / `jxDir` 对应目录下。 +2. 访问 `/api/:module` 接口时需通过 `validatePwd` 验证。 +3. `ext` 参数必须是 **Base64 编码的 JSON 字符串**,否则会报“筛选参数错误”。 +4. 流媒体内容可能会通过 `/mediaProxy` 重定向处理。 +5. 建议在请求时加上 `pg` 参数避免默认第一页。 diff --git a/dashboard/docs/mpv-protocol.reg b/dashboard/docs/mpv-protocol.reg new file mode 100644 index 0000000..9f50a4d --- /dev/null +++ b/dashboard/docs/mpv-protocol.reg @@ -0,0 +1,12 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CLASSES_ROOT\mpv] +@="URL:MPV Protocol" +"URL Protocol"="" + +[HKEY_CLASSES_ROOT\mpv\shell] + +[HKEY_CLASSES_ROOT\mpv\shell\open] + +[HKEY_CLASSES_ROOT\mpv\shell\open\command] +@="\"C:\\Program Files\\mpv\\mpv.exe\" \"%1\"" \ No newline at end of file diff --git a/dashboard/docs/nginx-root.conf b/dashboard/docs/nginx-root.conf new file mode 100644 index 0000000..38c6beb --- /dev/null +++ b/dashboard/docs/nginx-root.conf @@ -0,0 +1,97 @@ +# Nginx配置文件 - 根目录部署 +# 适用于将DrPlayer部署在域名根目录的情况 +# 例如: https://example.com/ + +server { + listen 80; + server_name localhost; # 请替换为您的域名 + + # 网站根目录,指向Vue构建后的dist目录 + root /var/www/html/drplayer; # 请替换为您的实际路径 + index index.html; + + # 启用gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # 静态资源缓存配置 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # API代理配置(如果需要) + # location /api/ { + # proxy_pass http://backend-server; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # } + + # Vue Router History模式支持 + # 所有不匹配静态文件的请求都返回index.html + location / { + try_files $uri $uri/ /index.html; + + # 安全头设置 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + } + + # 禁止访问隐藏文件 + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # 禁止访问备份文件 + location ~ ~$ { + deny all; + access_log off; + log_not_found off; + } + + # 错误页面 + error_page 404 /index.html; + error_page 500 502 503 504 /index.html; +} + +# HTTPS配置示例(可选) +# server { +# listen 443 ssl http2; +# server_name localhost; # 请替换为您的域名 +# +# ssl_certificate /path/to/your/certificate.crt; +# ssl_certificate_key /path/to/your/private.key; +# +# # SSL配置 +# ssl_protocols TLSv1.2 TLSv1.3; +# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; +# ssl_prefer_server_ciphers off; +# +# # 其他配置与HTTP相同 +# root /var/www/html/drplayer; +# index index.html; +# +# location / { +# try_files $uri $uri/ /index.html; +# } +# } \ No newline at end of file diff --git a/dashboard/docs/nginx-subdir.conf b/dashboard/docs/nginx-subdir.conf new file mode 100644 index 0000000..ed85d8c --- /dev/null +++ b/dashboard/docs/nginx-subdir.conf @@ -0,0 +1,115 @@ +# Nginx配置文件 - 子目录部署 +# 适用于将DrPlayer部署在域名子目录的情况 +# 例如: https://example.com/apps/drplayer/ + +server { + listen 80; + server_name localhost; # 请替换为您的域名 + + # 网站根目录 + root /var/www/html; + index index.html; + + # 启用gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # DrPlayer应用配置 - 子目录部署 + location /apps/drplayer/ { + alias /var/www/html/drplayer/; # 请替换为您的实际路径 + + # 静态资源缓存配置 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # Vue Router History模式支持 + # 所有不匹配静态文件的请求都返回index.html + try_files $uri $uri/ /apps/drplayer/index.html; + + # 安全头设置 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + } + + # 处理DrPlayer的根路径访问 + location = /apps/drplayer { + return 301 /apps/drplayer/; + } + + # API代理配置(如果需要) + # location /apps/drplayer/api/ { + # proxy_pass http://backend-server/api/; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # } + + # 其他应用或默认站点配置 + location / { + try_files $uri $uri/ =404; + } + + # 禁止访问隐藏文件 + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # 禁止访问备份文件 + location ~ ~$ { + deny all; + access_log off; + log_not_found off; + } + + # 错误页面 + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; +} + +# HTTPS配置示例(可选) +# server { +# listen 443 ssl http2; +# server_name localhost; # 请替换为您的域名 +# +# ssl_certificate /path/to/your/certificate.crt; +# ssl_certificate_key /path/to/your/private.key; +# +# # SSL配置 +# ssl_protocols TLSv1.2 TLSv1.3; +# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; +# ssl_prefer_server_ciphers off; +# +# # 其他配置与HTTP相同 +# root /var/www/html; +# index index.html; +# +# location /apps/drplayer/ { +# alias /var/www/html/drplayer/; +# try_files $uri $uri/ /apps/drplayer/index.html; +# } +# +# location = /apps/drplayer { +# return 301 /apps/drplayer/; +# } +# } \ No newline at end of file diff --git "a/dashboard/docs/pvideo\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/dashboard/docs/pvideo\346\216\245\345\217\243\350\257\264\346\230\216.md" new file mode 100644 index 0000000..ee716d4 --- /dev/null +++ "b/dashboard/docs/pvideo\346\216\245\345\217\243\350\257\264\346\230\216.md" @@ -0,0 +1,187 @@ +# MediaTool 参数说明和示例 + +## 1. 命令行支持的参数 + +### 可通过命令行传递的参数 + +| 参数 | 命令行标志 | 默认值 | 说明 | 示例 | +|------|------------|--------|------|------| +| config | `-config` | `"config.json"` | 配置文件路径 | `./pvideo -config myconfig.json` | +| port | `-port` | `"2525"` | 服务器端口 | `./pvideo -port 8080` | +| site | `-site` | `""` | 反代域名 | `./pvideo -site https://mydomain.com` | +| proxy | `-proxy` | `""` | 代理地址 | `./pvideo -proxy socks5://127.0.0.1:1080` | +| debug | `-debug` | `false` | 调试模式 | `./pvideo -debug` | +| dns | `-dns` | `""` | DNS服务器 | `./pvideo -dns 8.8.8.8` | + +### 只能通过配置文件设置的参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| localUrl | string | `""` | 本地服务URL,用于生成代理链接 | +| connect | int64 | `32` | 最大并发连接数 | +| ssl | object | `null` | SSL证书配置(cert, key) | + +### 命令行使用示例 + +```bash +# 基本启动 +./pvideo + +# 指定端口和调试模式 +./pvideo -port 8080 -debug + +# 使用SOCKS5代理 +./pvideo -proxy socks5://127.0.0.1:1080 -dns 8.8.8.8 + +# 完整参数示例 +./pvideo -config config.json -port 7777 -site https://mydomain.com -proxy http://proxy.example.com:8080 -debug -dns 8.8.8.8 +``` + +## 2. 多线程代理参数和示例 + +### 多线程代理支持的URL参数 + +| 参数 | 必需 | 说明 | 示例值 | +|------|------|------|--------| +| url | ✅ | 目标文件URL | `https://example.com/video.mp4` | +| form | ❌ | 编码格式 | `base64`(URL和header使用base64编码) | +| header | ❌ | 自定义请求头 | JSON格式的请求头 | +| thread | ❌ | 线程数 | `4`(默认根据文件大小自动计算) | +| size | ❌ | 分块大小 | `128K`(默认), `256K`, `1M`, `2M`, `512B`, `1024` | +| limit | ❌ | 限制条件 | `30S`(时间限制), `100C`(次数限制) | +| single | ❌ | 单线程模式 | `true`, `false` | +| mark | ❌ | 缓存标记 | 自定义缓存标识 | + +### 多线程代理使用示例(也可以不需要带proxy路径:http://localhost:7777?url=https://example.com/video.mp4) + +#### 基本用法 +``` +http://localhost:7777/proxy?url=https://example.com/video.mp4 +``` + +#### 自定义线程数和分块大小 +``` +http://localhost:7777/proxy?url=https://example.com/video.mp4&thread=8&size=256K +``` + +#### 使用自定义请求头 +``` +http://localhost:7777/proxy?url=https://example.com/video.mp4&header={"User-Agent":"Custom-Agent","Referer":"https://example.com"} +``` + +#### 使用base64编码(避免URL编码问题) +``` +http://localhost:7777/proxy?url=aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=&form=base64&header=eyJVc2VyLUFnZW50IjoiQ3VzdG9tLUFnZW50In0= +``` + +#### 设置时间限制(30秒后重新获取真实URL) +``` +http://localhost:7777/proxy?url=https://example.com/video.mp4&limit=30S +``` + +#### 设置次数限制(100次请求后重新获取真实URL) +``` +http://localhost:7777/proxy?url=https://example.com/video.mp4&limit=100C +``` + +#### 单线程模式(适用于例如网页请求) +``` +http://localhost:7777/proxy?single=true&url=https://example.com/small-file.jpg +``` + +#### 使用缓存标记(mark参数) +``` +# 任务1:普通下载 +http://localhost:7777/proxy?url=https://example.com/video.mp4&mark=normal_download + +# 任务2:带认证的下载(相同URL,不同处理方式) +http://localhost:7777/proxy?url=https://example.com/video.mp4&mark=auth_download&header={"Authorization":"Bearer token123"} + +# 任务3:移动端下载(相同URL,不同User-Agent) +http://localhost:7777/proxy?url=https://example.com/video.mp4&mark=mobile_download&header={"User-Agent":"Mobile-App/1.0"} +``` + +### 缓存标记(mark)说明 + +`mark` 参数是一个**缓存标识符**,用于区分和管理不同的下载任务: + +**主要作用**: +1. **缓存隔离**: 相同URL的不同任务使用独立缓存 +2. **连接复用**: 相同mark的请求复用HTTP连接 +3. **任务标识**: 便于调试和日志追踪 + +**使用场景**: + +#### 场景1: 相同文件的不同下载方式 +``` +# 高清版本 +http://localhost:7777/proxy?url=https://cdn.example.com/video.mp4&mark=hd_version&size=1M + +# 标清版本(相同URL,不同处理参数) +http://localhost:7777/proxy?url=https://cdn.example.com/video.mp4&mark=sd_version&size=256K +``` + +#### 场景2: 不同用户的相同资源 +``` +# 用户A下载 +http://localhost:7777/proxy?url=https://example.com/file.zip&mark=user_a&header={"Cookie":"session=abc123"} + +# 用户B下载 +http://localhost:7777/proxy?url=https://example.com/file.zip&mark=user_b&header={"Cookie":"session=def456"} +``` + +#### 场景3: 不同平台的相同内容 +``` +# PC端下载 +http://localhost:7777/proxy?url=https://example.com/app.apk&mark=pc_client + +# 移动端下载 +http://localhost:7777/proxy?url=https://example.com/app.apk&mark=mobile_client +``` + +**默认行为**: +- 如果不指定 `mark` 参数,系统会使用完整的 `url` 作为缓存标记 +- 建议在有多种下载需求时主动设置 `mark` 参数 + + + +## 3. M3U8参数和示例 + +### M3U8支持的URL参数 + +| 参数 | 必需 | 说明 | 示例值 | +|------|------|------|--------| +| url | ✅ | M3U8播放列表URL | `https://example.com/playlist.m3u8` | +| form | ❌ | 编码格式 | `base64`(URL和header使用base64编码) | +| header | ❌ | 自定义请求头 | JSON格式的请求头 | +| type | ❌ | 文件类型标识 | `m3u8` | + +### M3U8使用示例 + +#### 基本用法 +``` +http://localhost:7777/m3u8?url=https://example.com/playlist.m3u8 +``` + +#### 使用自定义请求头 +``` +http://localhost:7777/m3u8?url=https://example.com/playlist.m3u8&header={"User-Agent":"Mozilla/5.0","Referer":"https://example.com"} +``` + +#### 使用base64编码 +``` +http://localhost:7777/m3u8?url=aHR0cHM6Ly9leGFtcGxlLmNvbS9wbGF5bGlzdC5tM3U4&form=base64&header=eyJVc2VyLUFnZW50IjoiTW96aWxsYS81LjAifQ== +``` + +#### 指定类型标识 +``` +http://localhost:7777/m3u8?url=https://example.com/playlist.m3u8&type=m3u8 +``` + +### M3U8处理说明 + +1. **自动转换**: M3U8文件中的相对路径会自动转换为完整URL +2. **代理重写**: 所有媒体文件URL会被重写为通过本地代理访问 +3. **嵌套支持**: 支持嵌套的M3U8文件(主播放列表包含子播放列表) +4. **加密支持**: 支持AES加密的M3U8流,密钥URL也会被代理 +5. **Base64编码**: 当使用`form=base64`时,输出的代理URL也会使用base64编码 diff --git a/dashboard/docs/t4api.md b/dashboard/docs/t4api.md new file mode 100644 index 0000000..b0684ab --- /dev/null +++ b/dashboard/docs/t4api.md @@ -0,0 +1,310 @@ +# T4服务接口对接文档 + +## 概述 + +本文档描述了基于 `/api/:module` 路径的接口服务。该服务根据不同的参数组合调用不同的功能模块,包括播放、分类、详情、动作、搜索、刷新和默认首页数据。所有接口均支持GET和POST请求方式。 + +## 通用说明 + +- **请求方式**:GET 或 POST +- **基础路径**:`/api/:module`,其中 `:module` 为模块名称(如 `vod1`) +- **参数传递**: + - GET请求:参数通过URL的query string传递 + - POST请求:参数可通过`application/x-www-form-urlencoded`或`application/json`格式传递 +- **安全验证**:所有接口请求都会经过`validatePwd`中间件验证(具体验证逻辑由实现方决定) +- **错误响应**: + ```json + { + "error": "错误信息" + } + ``` +- **通用传参**: 若这个接口需要extend传参,确保每个地方调用都在各自的请求参数基础上加上原本的extend传参 + +## 接口列表 + +### 1. 播放接口 + +**功能说明**:根据播放ID和源标识获取视频播放地址及信息。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|------|----|--------|--------------------------| +| play | 是 | string | 播放ID | +| flag | 否 | string | 源标识,详情接口返回播放信息里有,播放时建议加上 | + +#### 请求示例 + +```http +GET /api/vod1?play=123&flag=youku +``` + +或 + +```http +POST /api/vod1 +Content-Type: application/x-www-form-urlencoded +play=123&flag=youku +``` + +#### 响应示例 + +```json +{ + "url": "https://example.com/video.mp4", + "type": "hls", + "headers": { + "Referer": "https://example.com" + } +} +``` + +### 2. 分类接口 + +**功能说明**:获取指定分类下的视频列表,支持分页和筛选条件。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|-----|----|---------|-------------------------------| +| ac | 是 | string | 固定值为 `list`(实际参数中需存在`ac`和`t`) | +| t | 是 | string | 分类ID | +| pg | 否 | integer | 页码,默认1 | +| ext | 否 | string | 筛选条件(base64编码的JSON字符串) | + +#### 请求示例 + +```http +GET /api/vod1?ac=cate&t=1&pg=2&ext=eyJuYW1lIjoi5paw5qW8In0= +``` + +#### 响应示例 + +```json +{ + "page": 2, + "pagecount": 10, + "list": [ + { + "vod_id": "101", + "vod_name": "电影名称", + "vod_pic": "https://example.com/pic.jpg" + } + ] +} +``` + +### 3. 详情接口 + +**功能说明**:获取一个或多个视频的详细信息。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|-----|----|--------|-----------------------------------| +| ac | 是 | string | 固定值为 `detail`(实际参数中需存在`ac`和`ids`) | +| ids | 是 | string | 视频ID,多个用逗号分隔 | + +#### 请求示例 + +```http +GET /api/vod1?ac=detail&ids=101,102 +``` + +或 + +```http +POST /api/vod1 +Content-Type: application/json +{ + "ac": "detail", + "ids": "101,102" +} +``` + +#### 响应示例 + +```json +[ + { + "vod_id": "101", + "vod_name": "电影名称", + "vod_actor": "主演", + "vod_content": "剧情简介", + "vod_play_url": "播放地址" + } +] +``` + +### 4. 动作接口 + +**功能说明**:执行特定动作(如收藏、点赞等)。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|--------|----|--------|--------------------------------------| +| ac | 是 | string | 固定值为 `action`(实际参数中需存在`ac`和`action`) | +| action | 是 | string | 动作类型(如 `like`, `collect`) | +| value | 是 | string | 动作值(如 `1` 表示执行) | + +#### 请求示例 + +```http +GET /api/vod1?ac=action&action=like&value=1 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +[交互UI说明参考此处](./ruleAttr.md) + +### 5. 搜索接口 + +**功能说明**:根据关键词搜索视频,支持快速搜索和分页。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|-------|----|---------|-----------------| +| wd | 是 | string | 搜索关键词 | +| quick | 否 | integer | 快速搜索模式(0或1,默认0) | +| pg | 否 | integer | 页码,默认1 | + +#### 请求示例 + +```http +GET /api/vod1?wd=电影&quick=1&pg=1 +``` + +#### 响应示例 + +```json +{ + "list": [ + { + "vod_id": "201", + "vod_name": "搜索到的电影", + "vod_remarks": "6.5分" + } + ], + "total": 50 +} +``` + +### 6. 刷新接口 + +**功能说明**:强制刷新模块的初始化数据。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|---------|----|--------|-----------| +| refresh | 是 | string | 任意值(存在即可) | + +#### 请求示例 + +```http +GET /api/vod1?refresh=1 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "刷新成功", + "data": { + "lastUpdate": "2023-08-01 12:00:00" + } +} +``` + +### 7. 默认首页接口 + +**功能说明**:获取模块的首页数据(包括home和homeVod数据)。当没有匹配到上述任何功能时,调用此接口。 + +#### 请求参数 + +| 参数名 | 必填 | 类型 | 说明 | +|--------|----|---------|------------------| +| filter | 否 | integer | 过滤条件(1表示启用,默认启用) | + +#### 请求示例 + +```http +GET /api/vod1 +``` + +或 + +```http +GET /api/vod1?filter=1 +``` + +#### 响应示例 + +```json +{ + "class": [ + { + "type_id": "choice", + "type_name": "精选" + }, + { + "type_id": "movie", + "type_name": "电影" + }, + { + "type_id": "tv", + "type_name": "电视剧" + }, + { + "type_id": "variety", + "type_name": "综艺" + }, + { + "type_id": "cartoon", + "type_name": "动漫" + }, + { + "type_id": "child", + "type_name": "少儿" + }, + { + "type_id": "doco", + "type_name": "纪录片" + } + ], + "filters": { + }, + "list": [ + { + "vod_id": "301", + "vod_name": "首页推荐电影", + "vod_pic": "https://example.com/recommend.jpg" + } + ] +} +``` + +## 错误状态码 + +| 状态码 | 含义 | 说明 | +|-----|----------------|-----------| +| 404 | Not Found | 模块不存在 | +| 500 | Internal Error | 服务器内部处理错误 | + +## 注意事项 + +1. 参数`ext`(在分类接口中)是base64编码的JSON字符串,用于传递筛选条件。 +2. 分页参数`pg`从1开始。 +3. 参数`extend`(接口数据扩展)从sites的ext直接取字符串,若有就每个接口都加上。 + 开发人员可参考此文档进行对接。 +4. 除`action`外的接口,尽量都用`get`协议,action由于传值可能较大推荐使用`post` \ No newline at end of file diff --git a/dashboard/docs/vlc-protocol.reg b/dashboard/docs/vlc-protocol.reg new file mode 100644 index 0000000..588b016 --- /dev/null +++ b/dashboard/docs/vlc-protocol.reg @@ -0,0 +1,12 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CLASSES_ROOT\vlc] +@="URL:VLC Protocol" +"URL Protocol"="" + +[HKEY_CLASSES_ROOT\vlc\shell] + +[HKEY_CLASSES_ROOT\vlc\shell\open] + +[HKEY_CLASSES_ROOT\vlc\shell\open\command] +@="\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" \"%1\"" \ No newline at end of file diff --git "a/dashboard/docs/\345\244\226\351\203\250\346\222\255\346\224\276\345\231\250\351\205\215\347\275\256\350\257\264\346\230\216.md" "b/dashboard/docs/\345\244\226\351\203\250\346\222\255\346\224\276\345\231\250\351\205\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 0000000..d1f04d3 --- /dev/null +++ "b/dashboard/docs/\345\244\226\351\203\250\346\222\255\346\224\276\345\231\250\351\205\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,96 @@ +# 外部播放器配置说明 + +本文档说明如何配置VLC和MPV播放器以支持自定义协议唤起。 + +## 配置步骤 + +### 1. 修改注册表文件中的播放器路径 + +根据您的实际安装路径修改注册表文件: + +#### vlc-protocol.reg +```reg +Windows Registry Editor Version 5.00 + +[HKEY_CLASSES_ROOT\vlc] +@="URL:VLC Protocol" +"URL Protocol"="" + +[HKEY_CLASSES_ROOT\vlc\shell] + +[HKEY_CLASSES_ROOT\vlc\shell\open] + +[HKEY_CLASSES_ROOT\vlc\shell\open\command] +@="\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" \"%1\"" +``` + +#### mpv-protocol.reg +```reg +Windows Registry Editor Version 5.00 + +[HKEY_CLASSES_ROOT\mpv] +@="URL:MPV Protocol" +"URL Protocol"="" + +[HKEY_CLASSES_ROOT\mpv\shell] + +[HKEY_CLASSES_ROOT\mpv\shell\open] + +[HKEY_CLASSES_ROOT\mpv\shell\open\command] +@="\"C:\\Program Files\\mpv\\mpv.exe\" \"%1\"" +``` + +**注意**: 请根据您的实际安装路径修改播放器的路径。常见路径: +- VLC: `C:\Program Files\VideoLAN\VLC\vlc.exe` +- MPV: `C:\Program Files\mpv\mpv.exe` 或 `C:\mpv\mpv.exe` + +### 2. 注册协议 + +以管理员身份运行以下注册表文件: + +1. 右键点击 `vlc-protocol.reg` → 选择"合并" +2. 右键点击 `mpv-protocol.reg` → 选择"合并" + +### 3. 测试配置 + +配置完成后,您可以在浏览器地址栏中测试: + +- VLC: `vlc://https://example.com/video.mp4` +- MPV: `mpv://https://example.com/video.mp4` + +## 工作原理 + +当您点击"用VLC播放"或"用MPV播放"按钮时: + +1. JavaScript代码构造 `vlc://` 或 `mpv://` 协议URL +2. 浏览器识别自定义协议并查找注册表中的处理程序 +3. 系统调用对应的播放器,并将完整URL作为参数传递 +4. 播放器接收到URL参数(如 `vlc://https://example.com/video.mp4`) +5. 播放器会自动处理协议前缀,提取真实的视频URL进行播放 + +## 常见问题 + +### Q: 如何确认配置是否成功? +A: 在浏览器地址栏输入测试URL,如果能够成功调起对应的播放器,说明配置成功。 + +### Q: 播放器路径不正确怎么办? +A: 请根据您的实际安装路径修改注册表文件中的播放器路径。 + +### Q: 为什么有时候播放器无法打开视频? +A: 可能是因为: +1. 视频URL需要特殊的请求头(如Referer) +2. 视频格式不被播放器支持 +3. 网络连接问题 + +## 卸载协议 + +如果需要卸载协议,可以创建卸载的注册表文件: + +```reg +Windows Registry Editor Version 5.00 + +[-HKEY_CLASSES_ROOT\vlc] +[-HKEY_CLASSES_ROOT\mpv] +``` + +保存为 `.reg` 文件并以管理员身份运行即可删除协议注册。 \ No newline at end of file diff --git "a/dashboard/docs/\350\207\252\345\256\232\344\271\211\351\241\265\351\235\242\347\273\204\344\273\266\346\212\200\346\234\257\350\247\204\350\214\203.md" "b/dashboard/docs/\350\207\252\345\256\232\344\271\211\351\241\265\351\235\242\347\273\204\344\273\266\346\212\200\346\234\257\350\247\204\350\214\203.md" new file mode 100644 index 0000000..2338f75 --- /dev/null +++ "b/dashboard/docs/\350\207\252\345\256\232\344\271\211\351\241\265\351\235\242\347\273\204\344\273\266\346\212\200\346\234\257\350\247\204\350\214\203.md" @@ -0,0 +1,691 @@ +# 自定义页面组件技术规范 + +## 概述 + +本文档定义了DrPlayer中用于渲染T4 Action交互功能的自定义页面组件技术规范。这些组件将根据后端返回的Action JSON配置动态生成相应的UI界面,实现灵活的用户交互体验。 + +## 架构设计 + +### 1. 组件层次结构 + +``` +ActionRenderer (主渲染器) +├── ActionDialog (弹窗容器) +│ ├── InputAction (单项输入组件) +│ ├── EditAction (多行编辑组件) +│ ├── MultiInputAction (多项输入组件) +│ ├── MenuAction (单选菜单组件) +│ ├── SelectAction (多选菜单组件) +│ ├── MsgBoxAction (消息弹窗组件) +│ ├── WebViewAction (网页视图组件) +│ └── HelpAction (帮助页面组件) +├── ActionButton (动作按钮组件) +└── ActionToast (提示消息组件) +``` + +### 2. 数据流 + +``` +Action JSON → ActionRenderer → 具体Action组件 → 用户交互 → 回调处理 → API调用 +``` + +## 核心组件规范 + +### 1. ActionRenderer (主渲染器) + +**职责:** +- 解析Action JSON配置 +- 根据type字段路由到对应的组件 +- 管理组件的生命周期 +- 处理专项动作 + +**Props接口:** +```typescript +interface ActionRendererProps { + actionData: string | ActionConfig; // Action JSON字符串或对象 + onAction: (action: string, value: any) => Promise; // 动作回调 + onClose?: () => void; // 关闭回调 +} + +interface ActionConfig { + actionId: string; + type: ActionType; + [key: string]: any; +} + +type ActionType = 'input' | 'edit' | 'multiInput' | 'multiInputX' | 'menu' | 'select' | 'msgbox' | 'webview' | 'help'; +``` + +**核心方法:** +```typescript +class ActionRenderer { + // 解析Action配置 + parseActionConfig(data: string | ActionConfig): ActionConfig; + + // 渲染对应组件 + renderActionComponent(config: ActionConfig): React.ReactNode; + + // 处理专项动作 + handleSpecialAction(actionId: string, config: ActionConfig): void; + + // 执行动作回调 + executeAction(action: string, value: any): Promise; +} +``` + +### 2. ActionDialog (弹窗容器) + +**职责:** +- 提供统一的弹窗容器 +- 处理弹窗的显示/隐藏逻辑 +- 管理弹窗的尺寸和位置 +- 处理背景遮罩和点击外部关闭 + +**Props接口:** +```typescript +interface ActionDialogProps { + visible: boolean; + title?: string; + width?: number; + height?: number; + bottom?: number; + canceledOnTouchOutside?: boolean; + dimAmount?: number; + onClose: () => void; + children: React.ReactNode; +} +``` + +**样式规范:** +```css +.action-dialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + z-index: 1000; +} + +.action-dialog-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; +} +``` + +### 3. InputAction (单项输入组件) + +**职责:** +- 渲染单项输入界面 +- 处理输入验证 +- 支持快速选择选项 +- 显示图片和二维码 +- 处理图片点击坐标 + +**Props接口:** +```typescript +interface InputActionProps { + config: InputActionConfig; + onSubmit: (value: any) => void; + onCancel?: () => void; +} + +interface InputActionConfig { + actionId: string; + id: string; + title: string; + tip: string; + value?: string; + msg?: string; + width?: number; + button?: number; + selectData?: string; + imageUrl?: string; + imageHeight?: number; + imageClickCoord?: boolean; + qrcode?: string; + qrcodeSize?: string; + timeout?: number; + keep?: boolean; + help?: string; +} +``` + +**核心功能:** +```typescript +class InputAction { + // 解析快速选择数据 + parseSelectData(selectData: string): SelectOption[]; + + // 处理图片点击坐标 + handleImageClick(event: MouseEvent): void; + + // 生成二维码 + generateQRCode(url: string, size: string): string; + + // 验证输入值 + validateInput(value: string): boolean; + + // 处理超时 + handleTimeout(timeout: number): void; +} +``` + +### 4. MultiInputAction (多项输入组件) + +**职责:** +- 渲染多项输入界面 +- 支持不同类型的输入控件 +- 处理文件选择、日期选择等特殊输入 +- 验证所有输入项 + +**Props接口:** +```typescript +interface MultiInputActionProps { + config: MultiInputActionConfig; + onSubmit: (values: Record) => void; + onCancel?: () => void; +} + +interface MultiInputActionConfig { + actionId: string; + type: 'multiInput' | 'multiInputX'; + title: string; + width?: number; + height?: number; + msg?: string; + button?: number; + input: InputItem[]; +} + +interface InputItem { + id: string; + name: string; + tip: string; + value?: string; + selectData?: string; + inputType?: number; + multiLine?: number; + validation?: string; + help?: string; + quickSelect?: boolean; + onlyQuickSelect?: boolean; + multiSelect?: boolean; + selectWidth?: number; + selectColumn?: number; +} +``` + +**特殊输入类型处理:** +```typescript +class MultiInputAction { + // 文件夹选择 + handleFolderSelect(itemId: string): void; + + // 文件选择 + handleFileSelect(itemId: string): void; + + // 日期选择 + handleDateSelect(itemId: string): void; + + // 图像选择并转换为Base64 + handleImageSelect(itemId: string): void; + + // 多选处理 + handleMultiSelect(itemId: string, options: string[]): void; + + // 输入验证 + validateItem(item: InputItem, value: string): boolean; +} +``` + +### 5. MenuAction (单选菜单组件) + +**职责:** +- 渲染单选菜单界面 +- 支持多列布局 +- 处理选项选择 + +**Props接口:** +```typescript +interface MenuActionProps { + config: MenuActionConfig; + onSelect: (action: string) => void; + onCancel?: () => void; +} + +interface MenuActionConfig { + actionId: string; + title: string; + width?: number; + column?: number; + option: MenuOption[]; + selectedIndex?: number; +} + +interface MenuOption { + name: string; + action: string; +} +``` + +### 6. SelectAction (多选菜单组件) + +**职责:** +- 渲染多选菜单界面 +- 管理选中状态 +- 支持全选/反选功能 + +**Props接口:** +```typescript +interface SelectActionProps { + config: SelectActionConfig; + onSubmit: (selectedActions: string[]) => void; + onCancel?: () => void; +} + +interface SelectActionConfig { + actionId: string; + title: string; + width?: number; + column?: number; + option: SelectOption[]; +} + +interface SelectOption { + name: string; + action: string; + selected?: boolean; +} +``` + +### 7. MsgBoxAction (消息弹窗组件) + +**职责:** +- 显示消息内容 +- 支持HTML格式消息 +- 显示图片 + +**Props接口:** +```typescript +interface MsgBoxActionProps { + config: MsgBoxActionConfig; + onClose: () => void; +} + +interface MsgBoxActionConfig { + actionId: string; + title: string; + msg?: string; + htmlMsg?: string; + imageUrl?: string; +} +``` + +### 8. WebViewAction (网页视图组件) + +**职责:** +- 嵌入网页内容 +- 处理网页交互 +- 管理缩放和滚动 + +**Props接口:** +```typescript +interface WebViewActionProps { + config: WebViewActionConfig; + onClose: () => void; +} + +interface WebViewActionConfig { + actionId: string; + url: string; + height?: number; + textZoom?: number; +} +``` + +## 状态管理 + +### 1. Action状态 + +```typescript +interface ActionState { + currentAction: ActionConfig | null; + isVisible: boolean; + isLoading: boolean; + error: string | null; + history: ActionConfig[]; // 动作历史,支持返回 +} +``` + +### 2. 状态管理器 + +```typescript +class ActionStateManager { + private state: ActionState; + private listeners: ((state: ActionState) => void)[]; + + // 显示动作 + showAction(config: ActionConfig): void; + + // 隐藏动作 + hideAction(): void; + + // 执行动作 + executeAction(action: string, value: any): Promise; + + // 返回上一个动作 + goBack(): void; + + // 清空历史 + clearHistory(): void; + + // 订阅状态变化 + subscribe(listener: (state: ActionState) => void): () => void; +} +``` + +## 样式规范 + +### 1. 设计系统 + +```css +:root { + /* 颜色 */ + --action-primary: #1890ff; + --action-success: #52c41a; + --action-warning: #faad14; + --action-error: #f5222d; + --action-text: #262626; + --action-text-secondary: #8c8c8c; + --action-border: #d9d9d9; + --action-background: #ffffff; + --action-background-light: #fafafa; + + /* 尺寸 */ + --action-border-radius: 6px; + --action-padding: 16px; + --action-margin: 8px; + + /* 阴影 */ + --action-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + --action-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.15); +} +``` + +### 2. 组件样式 + +```css +/* 输入框 */ +.action-input { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + font-size: 14px; + transition: border-color 0.3s; +} + +.action-input:focus { + border-color: var(--action-primary); + outline: none; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); +} + +/* 按钮 */ +.action-button { + padding: 8px 16px; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + background: var(--action-background); + cursor: pointer; + transition: all 0.3s; +} + +.action-button-primary { + background: var(--action-primary); + border-color: var(--action-primary); + color: white; +} + +.action-button:hover { + box-shadow: var(--action-shadow-hover); +} + +/* 选项 */ +.action-option { + padding: 12px; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + cursor: pointer; + transition: all 0.3s; +} + +.action-option:hover { + background: var(--action-background-light); + border-color: var(--action-primary); +} + +.action-option-selected { + background: var(--action-primary); + border-color: var(--action-primary); + color: white; +} +``` + +## API接口规范 + +### 1. Action执行接口 + +```typescript +interface ActionAPI { + // 执行动作 + executeAction(action: string, value: any): Promise; +} + +interface ActionResponse { + success: boolean; + data?: any; + action?: ActionConfig; // 新的动作配置 + toast?: string; // 提示消息 + error?: string; +} +``` + +### 2. 文件操作接口 + +```typescript +interface FileAPI { + // 选择文件夹 + selectFolder(): Promise; + + // 选择文件 + selectFile(accept?: string): Promise; + + // 选择图片并转换为Base64 + selectImageAsBase64(): Promise; +} +``` + +## 错误处理 + +### 1. 错误类型 + +```typescript +enum ActionErrorType { + PARSE_ERROR = 'PARSE_ERROR', + VALIDATION_ERROR = 'VALIDATION_ERROR', + NETWORK_ERROR = 'NETWORK_ERROR', + TIMEOUT_ERROR = 'TIMEOUT_ERROR', + USER_CANCEL = 'USER_CANCEL' +} + +interface ActionError { + type: ActionErrorType; + message: string; + details?: any; +} +``` + +### 2. 错误处理策略 + +```typescript +class ActionErrorHandler { + // 处理解析错误 + handleParseError(error: Error): void; + + // 处理验证错误 + handleValidationError(field: string, message: string): void; + + // 处理网络错误 + handleNetworkError(error: Error): void; + + // 处理超时错误 + handleTimeoutError(): void; + + // 显示错误提示 + showError(error: ActionError): void; +} +``` + +## 性能优化 + +### 1. 组件懒加载 + +```typescript +// 动态导入组件 +const InputAction = lazy(() => import('./InputAction')); +const MultiInputAction = lazy(() => import('./MultiInputAction')); +// ... + +// 使用Suspense包装 +}> + + +``` + +### 2. 虚拟滚动 + +对于大量选项的菜单组件,使用虚拟滚动优化性能: + +```typescript +import { FixedSizeList as List } from 'react-window'; + +const VirtualMenuAction = ({ options, onSelect }) => { + const Row = ({ index, style }) => ( +
onSelect(options[index])}> + {options[index].name} +
+ ); + + return ( + + {Row} + + ); +}; +``` + +### 3. 防抖处理 + +对于输入组件,使用防抖优化性能: + +```typescript +import { debounce } from 'lodash'; + +const debouncedValidation = debounce((value: string) => { + validateInput(value); +}, 300); +``` + +## 测试规范 + +### 1. 单元测试 + +```typescript +describe('ActionRenderer', () => { + test('should parse action config correctly', () => { + const config = '{"actionId":"test","type":"input"}'; + const result = ActionRenderer.parseActionConfig(config); + expect(result.actionId).toBe('test'); + expect(result.type).toBe('input'); + }); + + test('should handle invalid JSON gracefully', () => { + const config = 'invalid json'; + expect(() => ActionRenderer.parseActionConfig(config)).toThrow(); + }); +}); +``` + +### 2. 集成测试 + +```typescript +describe('Action Integration', () => { + test('should complete input action flow', async () => { + const mockOnAction = jest.fn().mockResolvedValue('success'); + const { getByTestId } = render( + + ); + + const input = getByTestId('action-input'); + fireEvent.change(input, { target: { value: 'test value' } }); + + const submitButton = getByTestId('action-submit'); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(mockOnAction).toHaveBeenCalledWith('test', { test: 'test value' }); + }); + }); +}); +``` + +## 部署和构建 + +### 1. 构建配置 + +```javascript +// vite.config.js +export default { + build: { + rollupOptions: { + output: { + manualChunks: { + 'action-components': [ + './src/components/actions/InputAction', + './src/components/actions/MultiInputAction', + // ... + ] + } + } + } + } +}; +``` + +### 2. 代码分割 + +```typescript +// 路由级别的代码分割 +const ActionPage = lazy(() => import('./pages/ActionPage')); + +// 组件级别的代码分割 +const ActionRenderer = lazy(() => import('./components/ActionRenderer')); +``` + +通过以上技术规范,开发团队可以构建出功能完整、性能优良、用户体验良好的Action交互组件系统。 \ No newline at end of file diff --git a/dashboard/index.html b/dashboard/index.html index eeedbd5..83911db 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -4,6 +4,7 @@ + DrPlayer diff --git a/dashboard/json/live_cntv.txt b/dashboard/json/live_cntv.txt new file mode 100644 index 0000000..aad576f --- /dev/null +++ b/dashboard/json/live_cntv.txt @@ -0,0 +1,62 @@ +央视频道,#genre# +CCTV4K,https://www.yangshipin.cn/tv/home?pid=600002264 +CCTV1,https://www.yangshipin.cn/tv/home?pid=600001859 +CCTV2,https://www.yangshipin.cn/tv/home?pid=600001800 +CCTV3(VIP),https://www.yangshipin.cn/tv/home?pid=600001801 +CCTV4,https://www.yangshipin.cn/tv/home?pid=600001814 +CCTV5(限免),https://www.yangshipin.cn/tv/home?pid=600001818 +CCTV5+(限免),https://www.yangshipin.cn/tv/home?pid=600001817 +CCTV6(VIP),https://www.yangshipin.cn/tv/home?pid=600001802 +CCTV7,https://www.yangshipin.cn/tv/home?pid=600004092 +CCTV8(VIP),https://www.yangshipin.cn/tv/home?pid=600001803 +CCTV9,https://www.yangshipin.cn/tv/home?pid=600004078 +CCTV10,https://www.yangshipin.cn/tv/home?pid=600001805 +CCTV11,https://www.yangshipin.cn/tv/home?pid=600001806 +CCTV12,https://www.yangshipin.cn/tv/home?pid=600001807 +CCTV13,https://www.yangshipin.cn/tv/home?pid=600001811 +CCTV14,https://www.yangshipin.cn/tv/home?pid=600001809 +CCTV15,https://www.yangshipin.cn/tv/home?pid=600001815 +CCTV16-HD,https://www.yangshipin.cn/tv/home?pid=600098637 +CCTV16(4K)(VIP),https://www.yangshipin.cn/tv/home?pid=600099502 +CCTV17,https://www.yangshipin.cn/tv/home?pid=600001810 +CGTN,https://www.yangshipin.cn/tv/home?pid=600014550 +CGTN法语频道,https://www.yangshipin.cn/tv/home?pid=600084704 +CGTN俄语频道,https://www.yangshipin.cn/tv/home?pid=600084758 +CGTN阿拉伯语频道,https://www.yangshipin.cn/tv/home?pid=600084782 +CGTN西班牙语频道,https://www.yangshipin.cn/tv/home?pid=600084744 +CGTN外语纪录频道,https://www.yangshipin.cn/tv/home?pid=600084781 +CCTV风云剧场频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099658 +CCTV第一剧场频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099655 +CCTV怀旧剧场频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099620 +CCTV世界地理频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099637 +CCTV风云音乐频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099660 +CCTV兵器科技频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099649 +CCTV风云足球频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099636 +CCTV高尔夫·网球频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099659 +CCTV女性时尚频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099650 +CCTV央视文化精品频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099653 +CCTV央视台球频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099652 +CCTV电视指南频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099656 +CCTV卫生健康频道(VIP),https://www.yangshipin.cn/tv/home?pid=600099651 +卫视频道,#genre# +北京卫视,https://www.yangshipin.cn/tv/home?pid=600002309 +江苏卫视,https://www.yangshipin.cn/tv/home?pid=600002521 +东方卫视,https://www.yangshipin.cn/tv/home?pid=600002483 +浙江卫视,https://www.yangshipin.cn/tv/home?pid=600002520 +湖南卫视,https://www.yangshipin.cn/tv/home?pid=600002475 +湖北卫视,https://www.yangshipin.cn/tv/home?pid=600002508 +广东卫视,https://www.yangshipin.cn/tv/home?pid=600002485 +广西卫视,https://www.yangshipin.cn/tv/home?pid=600002509 +黑龙江卫视,https://www.yangshipin.cn/tv/home?pid=600002498 +海南卫视,https://www.yangshipin.cn/tv/home?pid=600002506 +重庆卫视,https://www.yangshipin.cn/tv/home?pid=600002531 +深圳卫视,https://www.yangshipin.cn/tv/home?pid=600002481 +四川卫视,https://www.yangshipin.cn/tv/home?pid=600002516 +河南卫视,https://www.yangshipin.cn/tv/home?pid=600002525 +福建东南卫视,https://www.yangshipin.cn/tv/home?pid=600002484 +贵州卫视,https://www.yangshipin.cn/tv/home?pid=600002490 +江西卫视,https://www.yangshipin.cn/tv/home?pid=600002503 +辽宁卫视,https://www.yangshipin.cn/tv/home?pid=600002505 +安徽卫视,https://www.yangshipin.cn/tv/home?pid=600002532 +河北卫视,https://www.yangshipin.cn/tv/home?pid=600002493 +山东卫视,https://www.yangshipin.cn/tv/home?pid=600002513 \ No newline at end of file diff --git a/dashboard/json/tv.m3u b/dashboard/json/tv.m3u new file mode 100644 index 0000000..a5d1400 --- /dev/null +++ b/dashboard/json/tv.m3u @@ -0,0 +1,565 @@ +#EXTM3U x-tvg-url="https://epg.v1.mk/fy.xml" +#EXTINF:-1 tvg-name="4K60PSDR-H264-AAC测试" tvg-logo="https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/tg.jpg" group-title="4K频道",4K60PSDR-H264-AAC测试 +https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/sdr4kvideo/index.m3u8 +#EXTINF:-1 tvg-name="4K60PHLG-HEVC-EAC3测试" tvg-logo="https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/tg.jpg" group-title="4K频道",4K60PHLG-HEVC-EAC3测试 +https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/hlg4kvideo/index.m3u8 +#EXTINF:-1,tvg-id="CCTV1" tvg-name="CCTV1" tvg-logo="https://epg.v1.mk/logo/CCTV1.png" group-title="央视",cctv1-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv1.m3u8 +#EXTINF:-1,tvg-id="CCTV1" tvg-name="CCTV1" tvg-logo="https://epg.v1.mk/logo/CCTV1.png" group-title="央视",CCTV-1 +http://livetv.wqwqwq.sbs/itv/6000000001000029752.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv1" tvg-name="cctv1" tvg-logo="https://epg.v1.mk/logo/cctv1.png" group-title="央视",CCTV-1-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000002226.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="CCTV1" tvg-name="CCTV1" tvg-logo="https://epg.v1.mk/logo/CCTV1.png" group-title="央视",CCTV-1-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265001.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv2" tvg-name="cctv2" tvg-logo="https://epg.v1.mk/logo/cctv2.png" group-title="央视",cctv2-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv2.m3u8 +#EXTINF:-1,tvg-id="cctv2" tvg-name="cctv2" tvg-logo="https://epg.v1.mk/logo/cctv2.png" group-title="央视",CCTV-2 +http://livetv.wqwqwq.sbs/itv/1000000001000023315.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="CCTV2" tvg-name="CCTV2" tvg-logo="https://epg.v1.mk/logo/CCTV2.png" group-title="央视",CCTV-2-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000014161.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv2" tvg-name="cctv2" tvg-logo="https://epg.v1.mk/logo/cctv2.png" group-title="央视",CCTV-2-HEVC +http://livetv.wqwqwq.sbs/itv/1000000001000023315.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv3" tvg-name="cctv3" tvg-logo="https://epg.v1.mk/logo/cctv3.png" group-title="央视",cctv3-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv3.m3u8 +#EXTINF:-1,tvg-id="cctv3" tvg-name="cctv3" tvg-logo="https://epg.v1.mk/logo/cctv3.png" group-title="央视",CCTV-3 +http://livetv.wqwqwq.sbs/itv/6000000001000022313.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv3" tvg-name="cctv3" tvg-logo="https://epg.v1.mk/logo/cctv3.png" group-title="央视",CCTV-3-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265003.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv4" tvg-name="cctv4" tvg-logo="https://epg.v1.mk/logo/cctv4.png" group-title="央视",cctv4-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv4.m3u8 +#EXTINF:-1,tvg-id="CCTV4" tvg-name="CCTV4" tvg-logo="https://epg.v1.mk/logo/CCTV4.png" group-title="央视",CCTV-4-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000011000031102.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv4" tvg-name="cctv4" tvg-logo="https://epg.v1.mk/logo/cctv4.png" group-title="央视",CCTV-4-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265004.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv5" tvg-name="cctv5" tvg-logo="https://epg.v1.mk/logo/cctv5.png" group-title="央视",cctv5-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv5.m3u8 +#EXTINF:-1,tvg-id="cctv5" tvg-name="cctv5" tvg-logo="https://epg.v1.mk/logo/cctv5.png" group-title="央视",CCTV-5 +http://livetv.wqwqwq.sbs/itv/1000000005000025222.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv5" tvg-name="cctv5" tvg-logo="https://epg.v1.mk/logo/cctv5.png" group-title="央视",CCTV-5-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265005.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv5+" tvg-name="cctv5+" tvg-logo="https://epg.v1.mk/logo/cctv5+.png" group-title="央视",cctv5p-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv5p.m3u8 +#EXTINF:-1,tvg-id="cctv5+" tvg-name="cctv5+" tvg-logo="https://epg.v1.mk/logo/cctv5+.png" group-title="央视",CCTV-5+ +http://livetv.wqwqwq.sbs/itv/6000000001000015875.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv5+" tvg-name="cctv5+" tvg-logo="https://epg.v1.mk/logo/cctv5+.png" group-title="央视",CCTV-5+-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265016.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv6" tvg-name="cctv6" tvg-logo="https://epg.v1.mk/logo/cctv6.png" group-title="央视",CCTV-6 +http://livetv.wqwqwq.sbs/itv/1000000001000001737.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="CCTV6" tvg-name="CCTV6" tvg-logo="https://epg.v1.mk/logo/CCTV6.png" group-title="央视",CCTV-6-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000004574.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv6" tvg-name="cctv6" tvg-logo="https://epg.v1.mk/logo/cctv6.png" group-title="央视",CCTV-6-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265006.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="CCTV7" tvg-name="CCTV7" tvg-logo="https://epg.v1.mk/logo/CCTV7.png" group-title="央视",cctv7-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv7.m3u8 +#EXTINF:-1,tvg-id="CCTV7" tvg-name="CCTV7" tvg-logo="https://epg.v1.mk/logo/CCTV7.png" group-title="央视",CCTV-7 +http://livetv.wqwqwq.sbs/itv/1000000001000024341.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="CCTV7" tvg-name="CCTV7" tvg-logo="https://epg.v1.mk/logo/CCTV7.png" group-title="央视",CCTV-7-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000009055.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="CCTV7" tvg-name="CCTV7" tvg-logo="https://epg.v1.mk/logo/CCTV7.png" group-title="央视",CCTV-7-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265007.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv8" tvg-name="cctv8" tvg-logo="https://epg.v1.mk/logo/cctv8.png" group-title="央视",cctv8-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv8.m3u8 +#EXTINF:-1,tvg-id="cctv8" tvg-name="cctv8" tvg-logo="https://epg.v1.mk/logo/cctv8.png" group-title="央视",CCTV-8 +http://livetv.wqwqwq.sbs/itv/6000000001000001070.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv8" tvg-name="cctv8" tvg-logo="https://epg.v1.mk/logo/cctv8.png" group-title="央视",CCTV-8-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265008.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv9" tvg-name="cctv9" tvg-logo="https://epg.v1.mk/logo/cctv9.png" group-title="央视",cctv9-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv9.m3u8 +#EXTINF:-1,tvg-id="cctv9" tvg-name="cctv9" tvg-logo="https://epg.v1.mk/logo/cctv9.png" group-title="央视",CCTV-9 +http://livetv.wqwqwq.sbs/itv/1000000001000014583.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="CCTV9" tvg-name="CCTV9" tvg-logo="https://epg.v1.mk/logo/CCTV9.png" group-title="央视",CCTV-9-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000032162.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv9" tvg-name="cctv9" tvg-logo="https://epg.v1.mk/logo/cctv9.png" group-title="央视",CCTV-9-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265009.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv10" tvg-name="cctv10" tvg-logo="https://epg.v1.mk/logo/cctv10.png" group-title="央视",cctv10-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv10.m3u8 +#EXTINF:-1,tvg-id="cctv10" tvg-name="cctv10" tvg-logo="https://epg.v1.mk/logo/cctv10.png" group-title="央视",CCTV-10 +http://livetv.wqwqwq.sbs/itv/1000000001000023734.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv10" tvg-name="cctv10" tvg-logo="https://epg.v1.mk/logo/cctv10.png" group-title="央视",CCTV-10-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000012827.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv10" tvg-name="cctv10" tvg-logo="https://epg.v1.mk/logo/cctv10.png" group-title="央视",CCTV-10-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265010.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv11" tvg-name="cctv11" tvg-logo="https://epg.v1.mk/logo/cctv11.png" group-title="央视",cctv11-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv11.m3u8 +#EXTINF:-1,tvg-id="cctv11" tvg-name="cctv11" tvg-logo="https://epg.v1.mk/logo/cctv11.png" group-title="央视",CCTV-11-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000011000031106.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv11" tvg-name="cctv11" tvg-logo="https://epg.v1.mk/logo/cctv11.png" group-title="央视",CCTV-11-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265011.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv12" tvg-name="cctv12" tvg-logo="https://epg.v1.mk/logo/cctv12.png" group-title="央视",cctv12-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv12.m3u8 +#EXTINF:-1,tvg-id="cctv12" tvg-name="cctv12" tvg-logo="https://epg.v1.mk/logo/cctv12.png" group-title="央视",CCTV-12 +http://livetv.wqwqwq.sbs/itv/1000000001000032494.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv12" tvg-name="cctv12" tvg-logo="https://epg.v1.mk/logo/cctv12.png" group-title="央视",CCTV-12-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000022586.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="cctv12" tvg-name="cctv12" tvg-logo="https://epg.v1.mk/logo/cctv12.png" group-title="央视",CCTV-12-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265012.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv13" tvg-name="cctv13" tvg-logo="https://epg.v1.mk/logo/cctv13.png" group-title="央视",cctv13-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv13.m3u8 +#EXTINF:-1,tvg-id="cctv13" tvg-name="cctv13" tvg-logo="https://epg.v1.mk/logo/cctv13.png" group-title="央视",CCTV-13-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000011000031108.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv14" tvg-name="cctv14" tvg-logo="https://epg.v1.mk/logo/cctv14.png" group-title="央视",cctv14-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv14.m3u8 +#EXTINF:-1,tvg-id="cctv14" tvg-name="cctv14" tvg-logo="https://epg.v1.mk/logo/cctv14.png" group-title="央视",CCTV-14 +http://livetv.wqwqwq.sbs/itv/1000000001000008170.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv14" tvg-name="cctv14" tvg-logo="https://epg.v1.mk/logo/cctv14.png" group-title="央视",CCTV-14-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000006673.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv14" tvg-name="cctv14" tvg-logo="https://epg.v1.mk/logo/cctv14.png" group-title="央视",CCTV-14-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265013.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv15" tvg-name="cctv15" tvg-logo="https://epg.v1.mk/logo/cctv15.png" group-title="央视",cctv15-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv15.m3u8 +#EXTINF:-1,tvg-id="cctv15" tvg-name="cctv15" tvg-logo="https://epg.v1.mk/logo/cctv15.png" group-title="央视",CCTV-15-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000011000031109.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv15" tvg-name="cctv15" tvg-logo="https://epg.v1.mk/logo/cctv15.png" group-title="央视",CCTV-15-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265014.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv16" tvg-name="cctv16" tvg-logo="https://epg.v1.mk/logo/cctv16.png" group-title="央视",cctv16-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv16.m3u8 +#EXTINF:-1,tvg-id="cctv16" tvg-name="cctv16" tvg-logo="https://epg.v1.mk/logo/cctv16.png" group-title="央视",CCTV-16 +http://livetv.wqwqwq.sbs/itv/1000000006000233002.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv16" tvg-name="cctv16" tvg-logo="https://epg.v1.mk/logo/cctv16.png" group-title="4K频道",CCTV-16-4K-HEVC +http://livetv.wqwqwq.sbs/itv/5000000008000023254.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="cctv16" tvg-name="cctv16" tvg-logo="https://epg.v1.mk/logo/cctv16.png" group-title="4K频道",cctv164k_10m +http://livetv.wqwqwq.sbs/gaoma/cctv4k16_10m.m3u8 +#EXTINF:-1,tvg-id="cctv16" tvg-name="cctv16" tvg-logo="https://epg.v1.mk/logo/cctv16.png" group-title="4K频道",cctv164k +http://livetv.wqwqwq.sbs/gaoma/cctv4k16.m3u8 +#EXTINF:-1,tvg-id="cctv17" tvg-name="cctv17" tvg-logo="https://epg.v1.mk/logo/cctv17.png" group-title="央视",cctv17-高码 +http://livetv.wqwqwq.sbs/gaoma/cctv17.m3u8 +#EXTINF:-1,tvg-id="cctv17" tvg-name="cctv17" tvg-logo="https://epg.v1.mk/logo/cctv17.png" group-title="央视",CCTV-17 +http://livetv.wqwqwq.sbs/itv/1000000006000268004.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cctv17" tvg-name="cctv17" tvg-logo="https://epg.v1.mk/logo/cctv17.png" group-title="央视",CCTV-17-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265015.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="cgtn" tvg-name="cgtn" tvg-logo="https://epg.v1.mk/logo/cgtn.png" group-title="央视",CGTN +http://livetv.wqwqwq.sbs/itv/7745129417417101820.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="CGTN记录" tvg-name="CGTN记录" tvg-logo="https://epg.v1.mk/logo/CGTN记录.png" group-title="央视",CGTN-记录 +http://livetv.wqwqwq.sbs/itv/7114647837765104058.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="CGTNALBY" tvg-name="CGTNALBY" tvg-logo="https://epg.v1.mk/logo/CGTNALBY.png" group-title="央视",cgtnar-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtnar.m3u8 +#EXTINF:-1,tvg-id="cgtndocumentary" tvg-name="cgtndocumentary" tvg-logo="https://epg.v1.mk/logo/cgtndocumentary.png" group-title="央视",cgtndoc-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtndoc.m3u8 +#EXTINF:-1,tvg-id="CGTNEN" tvg-name="CGTNEN" tvg-logo="https://epg.v1.mk/logo/CGTN.png" group-title="央视",cgtnen-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtnen.m3u8 +#EXTINF:-1,tvg-id="cgtnfrench" tvg-name="cgtnfrench" tvg-logo="https://epg.v1.mk/logo/cgtnfrench.png" group-title="央视",cgtnfr-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtnfr.m3u8 +#EXTINF:-1,tvg-id="cgtnru" tvg-name="cgtnru" tvg-logo="https://epg.v1.mk/logo/CGTN1.png" group-title="央视",cgtnru-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtnru.m3u8 +#EXTINF:-1,tvg-id="cgtnsp" tvg-name="cgtnsp" tvg-logo="https://epg.v1.mk/logo/CGTN1.png" group-title="央视",cgtnsp-高码 +http://livetv.wqwqwq.sbs/gaoma/cgtnsp.m3u8 +#EXTINF:-1,tvg-id="重温经典" tvg-name="重温经典" tvg-logo="https://epg.v1.mk/logo/重温经典.png" group-title="其他",重温经典 +https://gdcucc.v1.mk/gdcucc/cwjd.m3u8 +#EXTINF:-1,tvg-id="cctv4k" tvg-name="cctv4k" tvg-logo="https://epg.v1.mk/logo/cctv4k.png" group-title="4K频道",cctv4k_10m +http://livetv.wqwqwq.sbs/gaoma/cctv4k_10m.m3u8 +#EXTINF:-1,tvg-id="cctv4k" tvg-name="cctv4k" tvg-logo="https://epg.v1.mk/logo/cctv4k.png" group-title="4K频道",cctv4k +http://livetv.wqwqwq.sbs/gaoma/cctv4k.m3u8 +#EXTINF:-1,tvg-id="北京纪实科教8K" tvg-name="北京纪实科教8K" tvg-logo="https://epg.v1.mk/logo/北京纪实科教8K.png" group-title="8K频道",北京纪实科教8K +https://gdcucc.v1.mk/gdcucc/brtv8k.m3u8 +#EXTINF:-1,tvg-id="cctv8k" tvg-name="cctv8k" tvg-logo="https://epg.v1.mk/logo/cctv8k.png" group-title="8K频道",cctv8k_36m +http://livetv.wqwqwq.sbs/gaoma/cctv8k_36m.m3u8 +#EXTINF:-1,tvg-id="cctv8k" tvg-name="cctv8k" tvg-logo="https://epg.v1.mk/logo/cctv8k.png" group-title="8K频道",cctv8k_120m +http://livetv.wqwqwq.sbs/gaoma/cctv8k_120m.m3u8 +#EXTINF:-1,tvg-id="中国教育1台" tvg-name="中国教育1台" tvg-logo="https://epg.v1.mk/logo/中国教育1台.png" group-title="其他",中国教育电视台-1 +http://livetv.wqwqwq.sbs/itv/5000000002000002652.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="中国教育4台" tvg-name="中国教育4台" tvg-logo="https://epg.v1.mk/logo/中国教育4台.png" group-title="其他",中国教育电视台-4 +http://livetv.wqwqwq.sbs/itv/5000000011000031126.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="北京卫视" tvg-name="北京卫视" tvg-logo="https://epg.v1.mk/logo/北京卫视.png" group-title="北京",北京卫视 +http://livetv.wqwqwq.sbs/itv/6000000001000020451.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="北京卫视" tvg-name="北京卫视" tvg-logo="https://epg.v1.mk/logo/北京卫视.png" group-title="北京",北京卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265027.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="北京纪实科教" tvg-name="北京纪实科教" tvg-logo="https://epg.v1.mk/logo/北京纪实科教.png" group-title="北京",北京纪实科教 +http://livetv.wqwqwq.sbs/itv/1000000001000001910.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="北京纪实科教" tvg-name="北京纪实科教" tvg-logo="https://epg.v1.mk/logo/北京纪实科教.png" group-title="北京",北京纪实科教-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265020.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="北京卡酷少儿" tvg-name="北京卡酷少儿" tvg-logo="https://epg.v1.mk/logo/北京卡酷少儿.png" group-title="北京",北京卡酷少儿 +http://livetv.wqwqwq.sbs/itv/7851974109718180595.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="安徽卫视" tvg-name="安徽卫视" tvg-logo="https://epg.v1.mk/logo/安徽卫视.png" group-title="安徽",安徽卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000030159.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="安徽卫视" tvg-name="安徽卫视" tvg-logo="https://epg.v1.mk/logo/安徽卫视.png" group-title="安徽",安徽卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000009954.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="安徽卫视" tvg-name="安徽卫视" tvg-logo="https://epg.v1.mk/logo/安徽卫视.png" group-title="安徽",安徽卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265025.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="东南卫视" tvg-name="东南卫视" tvg-logo="https://epg.v1.mk/logo/东南卫视.png" group-title="卫视",东南卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000010584.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="东南卫视" tvg-name="东南卫视" tvg-logo="https://epg.v1.mk/logo/东南卫视.png" group-title="卫视",东南卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265033.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="甘肃卫视" tvg-name="甘肃卫视" tvg-logo="https://epg.v1.mk/logo/甘肃卫视.png" group-title="甘肃",甘肃卫视 +http://livetv.wqwqwq.sbs/itv/5000000011000031121.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="广东卫视" tvg-name="广东卫视" tvg-logo="https://epg.v1.mk/logo/广东卫视.png" group-title="广东",广东卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000014176.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="广东卫视" tvg-name="广东卫视" tvg-logo="https://epg.v1.mk/logo/广东卫视.png" group-title="广东",广东卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000031076.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="广东卫视" tvg-name="广东卫视" tvg-logo="https://epg.v1.mk/logo/广东卫视.png" group-title="广东",广东卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265034.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="广西卫视" tvg-name="广西卫视" tvg-logo="https://epg.v1.mk/logo/广西卫视.png" group-title="卫视",广西卫视 +http://livetv.wqwqwq.sbs/itv/5000000011000031118.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="贵州卫视" tvg-name="贵州卫视" tvg-logo="https://epg.v1.mk/logo/贵州卫视.png" group-title="贵州",贵州卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000025843.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="海南卫视" tvg-name="海南卫视" tvg-logo="https://epg.v1.mk/logo/海南卫视.png" group-title="海南",海南卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000006211.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="河北卫视" tvg-name="河北卫视" tvg-logo="https://epg.v1.mk/logo/河北卫视.png" group-title="河北",河北卫视 +http://livetv.wqwqwq.sbs/itv/5000000006000040016.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="河南卫视" tvg-name="河南卫视" tvg-logo="https://epg.v1.mk/logo/河南卫视.png" group-title="河南",河南卫视 +http://livetv.wqwqwq.sbs/itv/5000000011000031119.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="黑龙江卫视" tvg-name="黑龙江卫视" tvg-logo="https://epg.v1.mk/logo/黑龙江卫视.png" group-title="黑龙江",黑龙江卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000001925.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="黑龙江卫视" tvg-name="黑龙江卫视" tvg-logo="https://epg.v1.mk/logo/黑龙江卫视.png" group-title="黑龙江",黑龙江卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000016510.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="黑龙江卫视" tvg-name="黑龙江卫视" tvg-logo="https://epg.v1.mk/logo/黑龙江卫视.png" group-title="黑龙江",黑龙江卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265029.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="湖北卫视" tvg-name="湖北卫视" tvg-logo="https://epg.v1.mk/logo/湖北卫视.png" group-title="湖北",湖北卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000024621.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="湖北卫视" tvg-name="湖北卫视" tvg-logo="https://epg.v1.mk/logo/湖北卫视.png" group-title="湖北",湖北卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000015436.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="湖北卫视" tvg-name="湖北卫视" tvg-logo="https://epg.v1.mk/logo/湖北卫视.png" group-title="湖北",湖北卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265023.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="湖南卫视" tvg-name="湖南卫视" tvg-logo="https://epg.v1.mk/logo/湖南卫视.png" group-title="湖南",湖南卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000006692.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="湖南卫视" tvg-name="湖南卫视" tvg-logo="https://epg.v1.mk/logo/湖南卫视.png" group-title="湖南",湖南卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000018044.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="湖南卫视" tvg-name="湖南卫视" tvg-logo="https://epg.v1.mk/logo/湖南卫视.png" group-title="湖南",湖南卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265024.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="湖南金鹰纪实" tvg-name="湖南金鹰纪实" tvg-logo="https://epg.v1.mk/logo/湖南金鹰纪实.png" group-title="湖南",湖南金鹰纪实 +http://livetv.wqwqwq.sbs/itv/5000000011000031203.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="湖南" tvg-name="湖南" tvg-logo="https://epg.v1.mk/logo/湖南.png" group-title="湖南",湖南快乐垂钓 +http://livetv.wqwqwq.sbs/itv/5000000011000031206.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="湖南茶频道" tvg-name="湖南茶频道" tvg-logo="https://epg.v1.mk/logo/湖南茶频道.png" group-title="湖南",湖南茶频道 +http://livetv.wqwqwq.sbs/itv/5000000011000031209.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="吉林卫视" tvg-name="吉林卫视" tvg-logo="https://epg.v1.mk/logo/吉林卫视.png" group-title="吉林",吉林卫视 +http://livetv.wqwqwq.sbs/itv/5000000011000031117.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="江苏卫视" tvg-name="江苏卫视" tvg-logo="https://epg.v1.mk/logo/江苏卫视.png" group-title="江苏",江苏卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000014861.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="江苏卫视" tvg-name="江苏卫视" tvg-logo="https://epg.v1.mk/logo/江苏卫视.png" group-title="江苏",江苏卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000001828.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="江苏卫视" tvg-name="江苏卫视" tvg-logo="https://epg.v1.mk/logo/江苏卫视.png" group-title="江苏",江苏卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265030.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="江西卫视" tvg-name="江西卫视" tvg-logo="https://epg.v1.mk/logo/江西卫视.png" group-title="江西",江西卫视 +http://livetv.wqwqwq.sbs/itv/1000000006000268001.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="江西卫视" tvg-name="江西卫视" tvg-logo="https://epg.v1.mk/logo/江西卫视.png" group-title="江西",江西卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265032.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="辽宁卫视" tvg-name="辽宁卫视" tvg-logo="https://epg.v1.mk/logo/辽宁卫视.png" group-title="辽宁",辽宁卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000011671.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="辽宁卫视" tvg-name="辽宁卫视" tvg-logo="https://epg.v1.mk/logo/辽宁卫视.png" group-title="辽宁",辽宁卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265022.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="青海卫视" tvg-name="青海卫视" tvg-logo="https://epg.v1.mk/logo/青海卫视.png" group-title="青海",青海卫视 +http://livetv.wqwqwq.sbs/itv/1000000002000013359.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="山东卫视" tvg-name="山东卫视" tvg-logo="https://epg.v1.mk/logo/山东卫视.png" group-title="山东",山东卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000016568.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="山东卫视" tvg-name="山东卫视" tvg-logo="https://epg.v1.mk/logo/山东卫视.png" group-title="山东",山东卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000004134.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="山东卫视" tvg-name="山东卫视" tvg-logo="https://epg.v1.mk/logo/山东卫视.png" group-title="山东",山东卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265019.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="东方卫视" tvg-name="东方卫视" tvg-logo="https://epg.v1.mk/logo/东方卫视.png" group-title="上海",上海东方卫视 +http://livetv.wqwqwq.sbs/itv/6000000001000003639.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="东方卫视" tvg-name="东方卫视" tvg-logo="https://epg.v1.mk/logo/东方卫视.png" group-title="上海",上海东方卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000014098.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="东方卫视" tvg-name="东方卫视" tvg-logo="https://epg.v1.mk/logo/东方卫视.png" group-title="上海",上海东方卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265018.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="上海ICS" tvg-name="上海ICS" tvg-logo="https://epg.v1.mk/logo/上海ICS.png" group-title="上海",上海-ICS-外语 +http://livetv.wqwqwq.sbs/itv/5000000010000030951.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="上海第一财经" tvg-name="上海第一财经" tvg-logo="https://epg.v1.mk/logo/上海第一财经.png" group-title="上海",上海第一财经 +http://livetv.wqwqwq.sbs/itv/5000000010000027146.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="东方财经" tvg-name="东方财经" tvg-logo="https://epg.v1.mk/logo/东方财经.png" group-title="上海",上海东方财经 +http://livetv.wqwqwq.sbs/itv/5000000007000010003.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="上海东方影视" tvg-name="上海东方影视" tvg-logo="https://epg.v1.mk/logo/上海东方影视.png" group-title="上海",上海东方影视 +http://livetv.wqwqwq.sbs/itv/5000000010000032212.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="上海都市" tvg-name="上海都市" tvg-logo="https://epg.v1.mk/logo/上海都市.png" group-title="上海",上海都市 +http://livetv.wqwqwq.sbs/itv/5000000010000018926.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="法治天地" tvg-name="法治天地" tvg-logo="https://epg.v1.mk/logo/法治天地.png" group-title="上海",上海法治天地 +http://livetv.wqwqwq.sbs/itv/2000000002000000014.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="哈哈炫动" tvg-name="哈哈炫动" tvg-logo="https://epg.v1.mk/logo/哈哈炫动.png" group-title="上海",上海哈哈炫动 +http://livetv.wqwqwq.sbs/itv/5000000011000031123.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="纪实人文" tvg-name="纪实人文" tvg-logo="https://epg.v1.mk/logo/纪实人文.png" group-title="上海",上海纪实人文-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000010282.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="纪实人文" tvg-name="纪实人文" tvg-logo="https://epg.v1.mk/logo/纪实人文.png" group-title="上海",上海纪实人文-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265021.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="五星体育" tvg-name="五星体育" tvg-logo="https://epg.v1.mk/logo/五星体育.png" group-title="上海",上海五星体育 +http://livetv.wqwqwq.sbs/itv/5000000010000017540.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="上海新闻综合" tvg-name="上海新闻综合" tvg-logo="https://epg.v1.mk/logo/上海新闻综合.png" group-title="上海",上海新闻综合 +http://livetv.wqwqwq.sbs/itv/5000000011000031110.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="深圳卫视" tvg-name="深圳卫视" tvg-logo="https://epg.v1.mk/logo/深圳卫视.png" group-title="卫视",深圳卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000007410.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="深圳卫视" tvg-name="深圳卫视" tvg-logo="https://epg.v1.mk/logo/深圳卫视.png" group-title="卫视",深圳卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000002116.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="深圳卫视" tvg-name="深圳卫视" tvg-logo="https://epg.v1.mk/logo/深圳卫视.png" group-title="卫视",深圳卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265028.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="四川卫视" tvg-name="四川卫视" tvg-logo="https://epg.v1.mk/logo/四川卫视.png" group-title="四川",四川卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000006119.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="天津卫视" tvg-name="天津卫视" tvg-logo="https://epg.v1.mk/logo/天津卫视.png" group-title="卫视",天津卫视 +http://livetv.wqwqwq.sbs/itv/5000000004000006827.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="天津卫视" tvg-name="天津卫视" tvg-logo="https://epg.v1.mk/logo/天津卫视.png" group-title="卫视",天津卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/6000000001000009186.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="天津卫视" tvg-name="天津卫视" tvg-logo="https://epg.v1.mk/logo/天津卫视.png" group-title="卫视",天津卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265026.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="云南卫视" tvg-name="云南卫视" tvg-logo="https://epg.v1.mk/logo/云南卫视.png" group-title="云南",云南卫视 +http://livetv.wqwqwq.sbs/itv/5000000011000031120.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="浙江卫视" tvg-name="浙江卫视" tvg-logo="https://epg.v1.mk/logo/浙江卫视.png" group-title="浙江",浙江卫视-50-FPS +http://livetv.wqwqwq.sbs/itv/5000000004000007275.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="浙江卫视" tvg-name="浙江卫视" tvg-logo="https://epg.v1.mk/logo/浙江卫视.png" group-title="浙江",浙江卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000014260.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="浙江卫视" tvg-name="浙江卫视" tvg-logo="https://epg.v1.mk/logo/浙江卫视.png" group-title="浙江",浙江卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265031.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="重庆卫视" tvg-name="重庆卫视" tvg-logo="https://epg.v1.mk/logo/重庆卫视.png" group-title="重庆",重庆卫视 +http://livetv.wqwqwq.sbs/itv/1000000001000001096.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="重庆卫视" tvg-name="重庆卫视" tvg-logo="https://epg.v1.mk/logo/重庆卫视.png" group-title="重庆",重庆卫视-HEVC +http://livetv.wqwqwq.sbs/itv/1000000005000265017.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="华数4K" tvg-name="华数4K" tvg-logo="https://epg.v1.mk/logo/华数4K.png" group-title="4K频道",华数-4K +http://livetv.wqwqwq.sbs/itv/6000000003000004748.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="纯享4K" tvg-name="纯享4K" tvg-logo="https://epg.v1.mk/logo/纯享4K.png" group-title="4K频道",纯享-4K +http://livetv.wqwqwq.sbs/itv/1000000004000011651.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4K" tvg-name="咪咕4K" tvg-logo="https://epg.v1.mk/logo/咪咕4K.png" group-title="4K频道",咪咕4K-1 +http://livetv.wqwqwq.sbs/itv/3000000010000005180.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4K" tvg-name="咪咕4K" tvg-logo="https://epg.v1.mk/logo/咪咕4K.png" group-title="4K频道",咪咕4K-2 +http://livetv.wqwqwq.sbs/itv/3000000010000015686.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="LOVENATURE" tvg-name="LOVENATURE" tvg-logo="https://epg.v1.mk/logo/LOVENATURE.png" group-title="4K频道",Love Nature 4K +https://d18dyiwu97wm6q.cloudfront.net/playlist2160p.m3u8 +#EXTINF:-1,tvg-id="LOUPE4K" tvg-name="LOUPE4K" tvg-logo="https://epg.v1.mk/logo/LOUPE4K.png" group-title="4K频道",Loupe 4K +https://d2dw21aq0j0l5c.cloudfront.net/playlist_3840x2160.m3u8 +#EXTINF:-1,tvg-id="FASHIONONE" tvg-name="FASHIONONE" tvg-logo="https://epg.v1.mk/logo/FASHIONONE.png" group-title="4K频道",Fashion 4K +https://fash2043.cloudycdn.services/slive/ftv_ftv_4k_hevc_73d_42080_default_466_hls.smil/playlist.m3u8 +#EXTINF:-1,tvg-id="咪咕全民热练" tvg-name="咪咕全民热练" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="其他",咪咕全民热练 +http://livetv.wqwqwq.sbs/itv/3000000020000031315.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="求索动物" tvg-name="求索动物" tvg-logo="https://epg.v1.mk/logo/求索动物.png" group-title="其他",求索动物 +http://livetv.wqwqwq.sbs/itv/6000000002000010046.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="求索纪录" tvg-name="求索纪录" tvg-logo="https://epg.v1.mk/logo/求索纪录.png" group-title="其他",求索纪录 +http://livetv.wqwqwq.sbs/itv/6000000002000032052.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="求索科学" tvg-name="求索科学" tvg-logo="https://epg.v1.mk/logo/求索科学.png" group-title="其他",求索科学 +http://livetv.wqwqwq.sbs/itv/6000000002000032344.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="求索生活" tvg-name="求索生活" tvg-logo="https://epg.v1.mk/logo/求索生活.png" group-title="其他",求索生活 +http://livetv.wqwqwq.sbs/itv/6000000002000003382.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="NewTV精品综合" tvg-name="NewTV精品综合" tvg-logo="https://epg.v1.mk/logo/NewTV精品综合.png" group-title="NEWTV",NewTV-精品综合 +http://livetv.wqwqwq.sbs/itv/1000000004000019008.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv精品大剧" tvg-name="newtv精品大剧" tvg-logo="https://epg.v1.mk/logo/newtv精品大剧.png" group-title="NEWTV",NewTV-精品大剧 +http://livetv.wqwqwq.sbs/itv/1000000004000013968.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv精品纪录" tvg-name="newtv精品纪录" tvg-logo="https://epg.v1.mk/logo/newtv精品纪录.png" group-title="NEWTV",NewTV-精品纪录 +http://livetv.wqwqwq.sbs/itv/1000000004000013730.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv精品体育" tvg-name="newtv精品体育" tvg-logo="https://epg.v1.mk/logo/newtv精品体育.png" group-title="NEWTV",NewTV-精品体育 +http://livetv.wqwqwq.sbs/itv/1000000004000014634.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NEWTV精品萌宠" tvg-name="NEWTV精品萌宠" tvg-logo="https://epg.v1.mk/logo/NEWTV精品萌宠.png" group-title="NEWTV",NewTV-精品萌宠 +http://livetv.wqwqwq.sbs/itv/1000000006000032328.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv爱情喜剧" tvg-name="newtv爱情喜剧" tvg-logo="https://epg.v1.mk/logo/newtv爱情喜剧.png" group-title="NEWTV",NewTV-爱情喜剧 +http://livetv.wqwqwq.sbs/itv/2000000003000000010.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="newtv超级电视剧" tvg-name="newtv超级电视剧" tvg-logo="https://epg.v1.mk/logo/newtv超级电视剧.png" group-title="NEWTV",NewTV-超级电视剧 +http://livetv.wqwqwq.sbs/itv/1000000006000268003.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv超级电影" tvg-name="newtv超级电影" tvg-logo="https://epg.v1.mk/logo/newtv超级电影.png" group-title="NEWTV",NewTV-超级电影 +http://livetv.wqwqwq.sbs/itv/1000000003000012426.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv超级体育" tvg-name="newtv超级体育" tvg-logo="https://epg.v1.mk/logo/newtv超级体育.png" group-title="NEWTV",NewTV-超级体育 +http://livetv.wqwqwq.sbs/itv/1000000001000009601.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv超级综艺" tvg-name="newtv超级综艺" tvg-logo="https://epg.v1.mk/logo/newtv超级综艺.png" group-title="NEWTV",NewTV-超级综艺 +http://livetv.wqwqwq.sbs/itv/1000000006000268002.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv潮妈辣婆" tvg-name="newtv潮妈辣婆" tvg-logo="https://epg.v1.mk/logo/newtv潮妈辣婆.png" group-title="NEWTV",NewTV-潮妈辣婆 +http://livetv.wqwqwq.sbs/itv/2000000003000000018.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="NewTV东北热剧" tvg-name="NewTV东北热剧" tvg-logo="https://epg.v1.mk/logo/NewTV东北热剧.png" group-title="NEWTV",NewTV-东北热剧 +http://livetv.wqwqwq.sbs/itv/1000000005000266013.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv动作电影" tvg-name="newtv动作电影" tvg-logo="https://epg.v1.mk/logo/newtv动作电影.png" group-title="NEWTV",NewTV-动作电影 +http://livetv.wqwqwq.sbs/itv/1000000004000018653.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NewTV古装剧场" tvg-name="NewTV古装剧场" tvg-logo="https://epg.v1.mk/logo/NewTV古装剧场.png" group-title="NEWTV",NewTV-古装剧场 +http://livetv.wqwqwq.sbs/itv/2000000003000000024.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="NewTV欢乐剧场" tvg-name="NewTV欢乐剧场" tvg-logo="https://epg.v1.mk/logo/NewTV欢乐剧场.png" group-title="NEWTV",NewTV-欢乐剧场 +http://livetv.wqwqwq.sbs/itv/1000000005000266012.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv家庭剧场" tvg-name="newtv家庭剧场" tvg-logo="https://epg.v1.mk/logo/newtv家庭剧场.png" group-title="NEWTV",NewTV-家庭剧场 +http://livetv.wqwqwq.sbs/itv/1000000004000008284.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NewTV金牌综艺" tvg-name="NewTV金牌综艺" tvg-logo="https://epg.v1.mk/logo/NewTV金牌综艺.png" group-title="NEWTV",NewTV-金牌综艺 +http://livetv.wqwqwq.sbs/itv/1000000004000026167.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv惊悚悬疑" tvg-name="newtv惊悚悬疑" tvg-logo="https://epg.v1.mk/logo/newtv惊悚悬疑.png" group-title="NEWTV",NewTV-惊悚悬疑 +http://livetv.wqwqwq.sbs/itv/1000000004000024282.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv军旅剧场" tvg-name="newtv军旅剧场" tvg-logo="https://epg.v1.mk/logo/newtv军旅剧场.png" group-title="NEWTV",NewTV-军旅剧场 +http://livetv.wqwqwq.sbs/itv/2000000003000000014.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="newtv军事评论" tvg-name="newtv军事评论" tvg-logo="https://epg.v1.mk/logo/newtv军事评论.png" group-title="NEWTV",NewTV-军事评论 +http://livetv.wqwqwq.sbs/itv/2000000003000000022.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="NewTV魅力潇湘" tvg-name="NewTV魅力潇湘" tvg-logo="https://epg.v1.mk/logo/NewTV魅力潇湘.png" group-title="NEWTV",NewTV-魅力潇湘 +http://livetv.wqwqwq.sbs/itv/1000000001000006197.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NewTV明星大片" tvg-name="NewTV明星大片" tvg-logo="https://epg.v1.mk/logo/NewTV明星大片.png" group-title="NEWTV",NewTV-明星大片 +http://livetv.wqwqwq.sbs/itv/2000000003000000016.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="newtv农业致富" tvg-name="newtv农业致富" tvg-logo="https://epg.v1.mk/logo/newtv农业致富.png" group-title="NEWTV",NewTV-农业致富 +http://livetv.wqwqwq.sbs/itv/2000000003000000003.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="NEWTV武博世界" tvg-name="NEWTV武博世界" tvg-logo="https://epg.v1.mk/logo/NEWTV武博世界.png" group-title="NEWTV",NewTV-武博世界 +http://livetv.wqwqwq.sbs/itv/2000000003000000007.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="newtv炫舞未来" tvg-name="newtv炫舞未来" tvg-logo="https://epg.v1.mk/logo/newtv炫舞未来.png" group-title="NEWTV",NewTV-炫舞未来 +http://livetv.wqwqwq.sbs/itv/1000000001000000515.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv怡伴健康" tvg-name="newtv怡伴健康" tvg-logo="https://epg.v1.mk/logo/newtv怡伴健康.png" group-title="NEWTV",NewTV-怡伴健康 +http://livetv.wqwqwq.sbs/itv/1000000005000266011.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="newtv中国功夫" tvg-name="newtv中国功夫" tvg-logo="https://epg.v1.mk/logo/newtv中国功夫.png" group-title="NEWTV",NewTV-中国功夫 +http://livetv.wqwqwq.sbs/itv/2000000003000000009.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="NEWTV黑莓电影" tvg-name="NEWTV黑莓电影" tvg-logo="https://epg.v1.mk/logo/NEWTV黑莓电影.png" group-title="NEWTV",NewTV-黑莓电影 +http://livetv.wqwqwq.sbs/itv/1000000004000019624.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NEWTV黑莓动画" tvg-name="NEWTV黑莓动画" tvg-logo="https://epg.v1.mk/logo/NEWTV黑莓动画.png" group-title="NEWTV",NewTV-黑莓动画 +http://livetv.wqwqwq.sbs/itv/1000000004000021734.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NEWTV哒啵电竞" tvg-name="NEWTV哒啵电竞" tvg-logo="https://epg.v1.mk/logo/哒啵电竞.png" group-title="NEWTV",NewTV-哒啵电竞 +http://livetv.wqwqwq.sbs/itv/1000000006000032327.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="NEWTV哒啵赛事" tvg-name="NEWTV哒啵赛事" tvg-logo="https://epg.v1.mk/logo/哒啵赛事.png" group-title="NEWTV",NewTV-哒啵赛事 +http://livetv.wqwqwq.sbs/itv/1000000001000003775.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="SITV动漫秀场" tvg-name="SITV动漫秀场" tvg-logo="https://epg.v1.mk/logo/SITV.png" group-title="其他",SiTV-动漫秀场 +http://livetv.wqwqwq.sbs/itv/5000000011000031113.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV都市剧场" tvg-name="SITV都市剧场" tvg-logo="https://epg.v1.mk/logo/SITV都市剧场.png" group-title="其他",SiTV-都市剧场 +http://livetv.wqwqwq.sbs/itv/5000000011000031111.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV法治天地" tvg-name="SITV法治天地" tvg-logo="https://epg.v1.mk/logo/SITV法治天地.png" group-title="其他",SiTV-法治天地 +http://livetv.wqwqwq.sbs/itv/9001547084732463424.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV欢笑剧场" tvg-name="SITV欢笑剧场" tvg-logo="https://epg.v1.mk/logo/SITV欢笑剧场.png" group-title="其他",SiTV-欢笑剧场 +http://livetv.wqwqwq.sbs/itv/5000000002000009455.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV欢笑剧场" tvg-name="SITV欢笑剧场" tvg-logo="https://epg.v1.mk/logo/SITV欢笑剧场.png" group-title="4K频道",SiTV-欢笑剧场-4K +http://livetv.wqwqwq.sbs/itv/5000000007000010001.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV金色学堂" tvg-name="SITV金色学堂" tvg-logo="https://epg.v1.mk/logo/SITV金色学堂.png" group-title="其他",SiTV-金色学堂 +http://livetv.wqwqwq.sbs/itv/5000000010000026105.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV劲爆体育" tvg-name="SITV劲爆体育" tvg-logo="https://epg.v1.mk/logo/SITV劲爆体育.png" group-title="其他",SiTV-劲爆体育 +http://livetv.wqwqwq.sbs/itv/5000000002000029972.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV乐游" tvg-name="SITV乐游" tvg-logo="https://epg.v1.mk/logo/SITV乐游.png" group-title="其他",SiTV-乐游 +http://livetv.wqwqwq.sbs/itv/5000000011000031112.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV魅力足球" tvg-name="SITV魅力足球" tvg-logo="https://epg.v1.mk/logo/SITV.png" group-title="其他",SiTV-魅力足球 +http://livetv.wqwqwq.sbs/itv/5000000011000031207.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV七彩戏剧" tvg-name="SITV七彩戏剧" tvg-logo="https://epg.v1.mk/logo/SITV七彩戏剧.png" group-title="其他",SiTV-七彩戏剧 +http://livetv.wqwqwq.sbs/itv/5000000011000031116.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV生活时尚" tvg-name="SITV生活时尚" tvg-logo="https://epg.v1.mk/logo/SITV.png" group-title="其他",SiTV-生活时尚 +http://livetv.wqwqwq.sbs/itv/5000000002000019634.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="SITV游戏风云" tvg-name="SITV游戏风云" tvg-logo="https://epg.v1.mk/logo/SITV.png" group-title="其他",SiTV-游戏风云 +http://livetv.wqwqwq.sbs/itv/5000000011000031114.m3u8?cdn=bestzb +#EXTINF:-1,tvg-id="IHOT爱电竞" tvg-name="IHOT爱电竞" tvg-logo="https://epg.v1.mk/logo/IHOT爱电竞.png" group-title="IHOT",iHOT-爱电竞 +http://livetv.wqwqwq.sbs/itv/6000000006000230630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱谍战" tvg-name="IHOT爱谍战" tvg-logo="https://epg.v1.mk/logo/IHOT爱谍战.png" group-title="IHOT",iHOT-爱谍战 +http://livetv.wqwqwq.sbs/itv/6000000006000070630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱动漫" tvg-name="IHOT爱动漫" tvg-logo="https://epg.v1.mk/logo/IHOT爱动漫.png" group-title="IHOT",iHOT-爱动漫 +http://livetv.wqwqwq.sbs/itv/6000000006000280630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱都市" tvg-name="IHOT爱都市" tvg-logo="https://epg.v1.mk/logo/IHOT爱都市.png" group-title="IHOT",iHOT-爱都市 +http://livetv.wqwqwq.sbs/itv/6000000006000080630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱怀旧" tvg-name="IHOT爱怀旧" tvg-logo="https://epg.v1.mk/logo/IHOT爱怀旧.png" group-title="IHOT",iHOT-爱怀旧 +http://livetv.wqwqwq.sbs/itv/6000000006000260630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱经典" tvg-name="IHOT爱经典" tvg-logo="https://epg.v1.mk/logo/IHOT爱经典.png" group-title="IHOT",iHOT-爱经典 +http://livetv.wqwqwq.sbs/itv/6000000006000060630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱科幻" tvg-name="IHOT爱科幻" tvg-logo="https://epg.v1.mk/logo/IHOT爱科幻.png" group-title="IHOT",iHOT-爱科幻 +http://livetv.wqwqwq.sbs/itv/6000000006000020630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱科学" tvg-name="IHOT爱科学" tvg-logo="https://epg.v1.mk/logo/IHOT爱科学.png" group-title="IHOT",iHOT-爱科学 +http://livetv.wqwqwq.sbs/itv/6000000006000160630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱浪漫" tvg-name="IHOT爱浪漫" tvg-logo="https://epg.v1.mk/logo/IHOT爱浪漫.png" group-title="IHOT",iHOT-爱浪漫 +http://livetv.wqwqwq.sbs/itv/6000000006000040630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱历史" tvg-name="IHOT爱历史" tvg-logo="https://epg.v1.mk/logo/IHOT爱历史.png" group-title="IHOT",iHOT-爱历史 +http://livetv.wqwqwq.sbs/itv/6000000006000150630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱旅行" tvg-name="IHOT爱旅行" tvg-logo="https://epg.v1.mk/logo/IHOT爱旅行.png" group-title="IHOT",iHOT-爱旅行 +http://livetv.wqwqwq.sbs/itv/6000000006000250630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱奇谈" tvg-name="IHOT爱奇谈" tvg-logo="https://epg.v1.mk/logo/IHOT爱奇谈.png" group-title="IHOT",iHOT-爱奇谈 +http://livetv.wqwqwq.sbs/itv/6000000006000270630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱青春" tvg-name="IHOT爱青春" tvg-logo="https://epg.v1.mk/logo/IHOT爱青春.png" group-title="IHOT",iHOT-爱青春 +http://livetv.wqwqwq.sbs/itv/6000000006000100630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱赛车" tvg-name="IHOT爱赛车" tvg-logo="https://epg.v1.mk/logo/IHOT爱赛车.png" group-title="IHOT",iHOT-爱赛车 +http://livetv.wqwqwq.sbs/itv/6000000006000240630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱体育" tvg-name="IHOT爱体育" tvg-logo="https://epg.v1.mk/logo/IHOT爱体育.png" group-title="IHOT",iHOT-爱体育 +http://livetv.wqwqwq.sbs/itv/6000000006000290630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱玩具" tvg-name="IHOT爱玩具" tvg-logo="https://epg.v1.mk/logo/IHOT爱玩具.png" group-title="IHOT",iHOT-爱玩具 +http://livetv.wqwqwq.sbs/itv/6000000006000220630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱喜剧" tvg-name="IHOT爱喜剧" tvg-logo="https://epg.v1.mk/logo/IHOT爱喜剧.png" group-title="IHOT",iHOT-爱喜剧 +http://livetv.wqwqwq.sbs/itv/6000000006000010630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱悬疑" tvg-name="IHOT爱悬疑" tvg-logo="https://epg.v1.mk/logo/IHOT爱悬疑.png" group-title="IHOT",iHOT-爱悬疑 +http://livetv.wqwqwq.sbs/itv/6000000006000050630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱幼教" tvg-name="IHOT爱幼教" tvg-logo="https://epg.v1.mk/logo/IHOT爱幼教.png" group-title="IHOT",iHOT-爱幼教 +http://livetv.wqwqwq.sbs/itv/6000000006000180630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="IHOT爱院线" tvg-name="IHOT爱院线" tvg-logo="https://epg.v1.mk/logo/IHOT爱院线.png" group-title="IHOT",iHOT-爱院线 +http://livetv.wqwqwq.sbs/itv/6000000006000030630.m3u8?cdn=wasusyt +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-1 +http://livetv.wqwqwq.sbs/itv/3000000001000005308.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-2 +http://livetv.wqwqwq.sbs/itv/3000000001000005969.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-3 +http://livetv.wqwqwq.sbs/itv/3000000001000007218.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-4 +http://livetv.wqwqwq.sbs/itv/3000000001000008001.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-5 +http://livetv.wqwqwq.sbs/itv/3000000001000008176.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-6 +http://livetv.wqwqwq.sbs/itv/3000000001000008379.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-7 +http://livetv.wqwqwq.sbs/itv/3000000001000010129.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-8 +http://livetv.wqwqwq.sbs/itv/3000000001000010948.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-9 +http://livetv.wqwqwq.sbs/itv/3000000001000028638.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-10 +http://livetv.wqwqwq.sbs/itv/3000000001000031494.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-11 +http://livetv.wqwqwq.sbs/itv/3000000010000005837.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-12 +http://livetv.wqwqwq.sbs/itv/3000000020000011518.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-13 +http://livetv.wqwqwq.sbs/itv/3000000020000011519.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-14 +http://livetv.wqwqwq.sbs/itv/3000000020000011520.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-15 +http://livetv.wqwqwq.sbs/itv/3000000020000011521.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-16 +http://livetv.wqwqwq.sbs/itv/3000000020000011522.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-17 +http://livetv.wqwqwq.sbs/itv/3000000020000011523.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-18 +http://livetv.wqwqwq.sbs/itv/3000000020000011524.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-19 +http://livetv.wqwqwq.sbs/itv/3000000020000011525.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-20 +http://livetv.wqwqwq.sbs/itv/3000000020000011528.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-21 +http://livetv.wqwqwq.sbs/itv/3000000020000011529.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-22 +http://livetv.wqwqwq.sbs/itv/3000000020000011530.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-23 +http://livetv.wqwqwq.sbs/itv/3000000020000011531.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-24 +http://livetv.wqwqwq.sbs/itv/3000000010000000097.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-25 +http://livetv.wqwqwq.sbs/itv/3000000010000002019.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-26 +http://livetv.wqwqwq.sbs/itv/3000000010000002809.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-27 +http://livetv.wqwqwq.sbs/itv/3000000010000003915.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-28 +http://livetv.wqwqwq.sbs/itv/3000000010000004193.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-29 +http://livetv.wqwqwq.sbs/itv/3000000010000006077.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-30 +http://livetv.wqwqwq.sbs/itv/3000000010000006658.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-31 +http://livetv.wqwqwq.sbs/itv/3000000010000009788.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-32 +http://livetv.wqwqwq.sbs/itv/3000000010000010833.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-33 +http://livetv.wqwqwq.sbs/itv/3000000010000011297.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-34 +http://livetv.wqwqwq.sbs/itv/3000000010000011518.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-35 +http://livetv.wqwqwq.sbs/itv/3000000010000012558.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-36 +http://livetv.wqwqwq.sbs/itv/3000000010000012616.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-37 +http://livetv.wqwqwq.sbs/itv/3000000010000015470.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-38 +http://livetv.wqwqwq.sbs/itv/3000000010000015560.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-39 +http://livetv.wqwqwq.sbs/itv/3000000010000017678.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-40 +http://livetv.wqwqwq.sbs/itv/3000000010000019839.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-41 +http://livetv.wqwqwq.sbs/itv/3000000010000021904.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-42 +http://livetv.wqwqwq.sbs/itv/3000000010000023434.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-43 +http://livetv.wqwqwq.sbs/itv/3000000010000025380.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-44 +http://livetv.wqwqwq.sbs/itv/3000000010000027691.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_咪视通-45 +http://livetv.wqwqwq.sbs/itv/3000000010000031669.m3u8?cdn=FifastbLive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-1 +http://livetv.wqwqwq.sbs/itv/2000000003000000060.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-2 +http://livetv.wqwqwq.sbs/itv/2000000003000000061.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-3 +http://livetv.wqwqwq.sbs/itv/2000000003000000062.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-4 +http://livetv.wqwqwq.sbs/itv/2000000003000000063.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-5 +http://livetv.wqwqwq.sbs/itv/2000000003000000064.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_湖南bblive-6 +http://livetv.wqwqwq.sbs/itv/2000000003000000065.m3u8?cdn=hnbblive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-1 +http://livetv.wqwqwq.sbs/itv/1000000006000270001.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-2 +http://livetv.wqwqwq.sbs/itv/1000000006000270002.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-3 +http://livetv.wqwqwq.sbs/itv/1000000006000270003.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-4 +http://livetv.wqwqwq.sbs/itv/1000000006000270004.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-5 +http://livetv.wqwqwq.sbs/itv/1000000006000270005.m3u8?cdn=ystenlive +#EXTINF:-1,tvg-id="咪咕4k" tvg-name="咪咕4k" tvg-logo="https://epg.v1.mk/logo/咪咕4k.png" group-title="咪咕体育",咪咕视频_8M1080_易视腾-6 +http://livetv.wqwqwq.sbs/itv/1000000006000270006.m3u8?cdn=ystenlive diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index b43e5fb..74a3235 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1,22 +1,52 @@ { "name": "dashboard", - "version": "0.0.0", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dashboard", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { - "@primevue/themes": "^4.2.1", - "primevue": "^4.2.1", - "vue": "^3.5.12" + "@fastify/static": "^8.2.0", + "artplayer": "^5.3.0", + "axios": "1.12.0", + "echarts": "^5.6.0", + "fastify": "^5.6.1", + "flv.js": "^1.6.2", + "hls.js": "^1.6.13", + "json-server": "^0.17.4", + "pinia": "^2.2.6", + "shaka-player": "^4.16.3", + "v-viewer": "^3.0.22", + "viewerjs": "^1.11.0", + "vue": "^3.5.12", + "vue-echarts": "^7.0.3", + "vue-router": "^4.4.5" }, "devDependencies": { - "@primevue/auto-import-resolver": "^4.2.1", - "@vitejs/plugin-vue": "^5.1.4", + "@arco-design/web-vue": "^2.56.3", + "@playwright/test": "^1.55.1", + "@tailwindcss/postcss": "^4.1.13", + "@vitejs/plugin-vue": "^6.0.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.13", "unplugin-vue-components": "^0.27.4", - "vite": "^5.4.10" + "vite": "^7.1.7" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@antfu/utils": { @@ -29,31 +59,62 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@arco-design/color": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@arco-design/color/-/color-0.4.0.tgz", + "integrity": "sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==", + "dev": true, + "license": "MIT", + "dependencies": { + "color": "^3.1.3" + } + }, + "node_modules/@arco-design/web-vue": { + "version": "2.57.0", + "resolved": "https://registry.npmmirror.com/@arco-design/web-vue/-/web-vue-2.57.0.tgz", + "integrity": "sha512-R5YReC3C2sG3Jv0+YuR3B7kzkq2KdhhQNCGXD8T11xAoa0zMt6SWTP1xJQOdZcM9du+q3z6tk5mRvh4qkieRJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arco-design/color": "^0.4.0", + "b-tween": "^0.3.3", + "b-validate": "^1.5.3", + "compute-scroll-into-view": "^1.0.20", + "dayjs": "^1.11.13", + "number-precision": "^1.6.0", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.31", + "vue": "^3.1.0" + }, + "peerDependencies": { + "vue": "^3.1.0" + } + }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -63,22 +124,22 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.10", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", "cpu": [ "x64" ], @@ -89,941 +150,3961 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT" }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", + "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "fast-json-stringify": "^6.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "dequal": "^2.0.3" } }, - "node_modules/@primeuix/styled": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.3.0.tgz", - "integrity": "sha512-XsLbmyM1u50A0EDATIHyqm5O/zOCSyNKPk4pNN8HFvEPehbsjf4tkXcRZAyaVvntSCLpV4XGAj7v5EDCQkBRlg==", + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "@primeuix/utils": "^0.3.0" - }, - "engines": { - "node": ">=12.11.0" + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" } }, - "node_modules/@primeuix/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-d6ymWez1n+iqwzAVhyOTmrOHl5qnSX2oGlTy97qGuA15gLai+MQaxONHFNdDia8Q7o396v7KK9IvhAx9VET/+A==", + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", - "engines": { - "node": ">=12.11.0" + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" } }, - "node_modules/@primevue/auto-import-resolver": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@primevue/auto-import-resolver/-/auto-import-resolver-4.2.1.tgz", - "integrity": "sha512-NR2jTme+R/p9oapvysh4y8vFR6/w2ymK4lOzx+pZHHb+QGh8lJvkvJ5NWhwOhO9YD4RGOlQGKA4kcY2Z1itafA==", - "dev": true, + "node_modules/@fastify/static": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/@fastify/static/-/static-8.2.0.tgz", + "integrity": "sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "@primevue/metadata": "4.2.1" - }, + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", "engines": { - "node": ">=12.11.0" + "node": "20 || >=22" } }, - "node_modules/@primevue/core": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.2.1.tgz", - "integrity": "sha512-L81TZSZU8zRznIi2g6IWwlZ5wraaE8DrNUJyxieCRCTpbSF3rSlYmhDEuzal8PfE0RuvXpRsxqedTHxz5cdqPg==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "license": "MIT", "dependencies": { - "@primeuix/styled": "^0.3.0", - "@primeuix/utils": "^0.3.0" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12.11.0" - }, - "peerDependencies": { - "vue": "^3.3.0" + "node": "20 || >=22" } }, - "node_modules/@primevue/icons": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@primevue/icons/-/icons-4.2.1.tgz", - "integrity": "sha512-TOhxgkcmgBqmlHlf2x+gs4874iHopkow0gRAC5FztZTgTZQrqy8hPIA9b4O1lW7P6GOjGuVIwSH8y2lw6Q8koA==", - "license": "MIT", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { - "@primeuix/utils": "^0.3.0", - "@primevue/core": "4.2.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=12.11.0" + "node": ">=12" } }, - "node_modules/@primevue/metadata": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@primevue/metadata/-/metadata-4.2.1.tgz", - "integrity": "sha512-XX29c2FtbXo0EX8GoYYT9os0FMxAZBPqq6VTAhbHrIUWzKnR8SVrxWoyy6G0wbzP3qXD4X3T7wUhjvQYHtTzLg==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { - "node": ">=12.11.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@primevue/themes": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@primevue/themes/-/themes-4.2.1.tgz", - "integrity": "sha512-byp4YejyVdrOpRRbq5vBtaDBFHUq7Wc0aGWwII1fliYbwQ+WXn/hCAYhaXwRrwweHpTiobiWWsS+PRLWJ7fBRw==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "dependencies": { - "@primeuix/styled": "^0.3.0" - }, "engines": { - "node": ">=12.11.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.4", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz", - "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.1.4", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", - "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": ">=12" }, - "peerDependencies": { - "vite": "^5.0.0", - "vue": "^3.2.25" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.12.tgz", - "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", - "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", - "license": "MIT", + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", - "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.12", - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.47", - "source-map-js": "^1.2.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", - "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.12" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@vue/runtime-core": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.12.tgz", - "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", - "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", - "csstype": "^3.1.3" + "engines": { + "node": ">=8" } }, - "node_modules/@vue/server-renderer": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.12.tgz", - "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, - "peerDependencies": { - "vue": "3.5.12" + "engines": { + "node": ">= 8" } }, - "node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 8" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { "node": ">= 8" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/@playwright/test": { + "version": "1.55.1", + "resolved": "https://registry.npmmirror.com/@playwright/test/-/test-1.55.1.tgz", + "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.55.1" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", "dev": true, "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, "engines": { - "node": ">=8" + "node": ">=14.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", + "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", + "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.1.13.tgz", + "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.18", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.13" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@tailwindcss/oxide": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.1.13.tgz", + "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "detect-libc": "^2.0.4", + "tar": "^7.4.3" }, "engines": { - "node": ">=8" + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-x64": "4.1.13", + "@tailwindcss/oxide-freebsd-x64": "4.1.13", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", + "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@tailwindcss/postcss/-/postcss-4.1.13.tgz", + "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "postcss": "^8.4.41", + "tailwindcss": "4.1.13" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" }, "engines": { - "node": ">= 8.10.0" + "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", + "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/compiler-core": "3.5.22", + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.19", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", + "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.22.tgz", + "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.22.tgz", + "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", + "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/runtime-core": "3.5.22", + "@vue/shared": "3.5.22", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.22.tgz", + "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependencies": { + "vue": "3.5.22" } }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", "license": "MIT" }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", "license": "MIT" }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=6.0" + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "ajv": { "optional": true } } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { - "node": ">=0.12" + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/artplayer": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/artplayer/-/artplayer-5.3.0.tgz", + "integrity": "sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==", + "license": "MIT", + "dependencies": { + "option-validator": "^2.0.6" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/axios": { + "version": "1.12.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.0.tgz", + "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b-tween": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/b-tween/-/b-tween-0.3.3.tgz", + "integrity": "sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/b-validate": { + "version": "1.5.3", + "resolved": "https://registry.npmmirror.com/b-validate/-/b-validate-1.5.3.tgz", + "integrity": "sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.9", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", + "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001745", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", + "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-pause": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/connect-pause/-/connect-pause-0.1.1.tgz", + "integrity": "sha512-a1gSWQBQD73krFXdUEYJom2RTFrWUL3YvXDCRkyv//GVXc79cdW9MngtRuN9ih4FDKBtfJAJId+BbDuX+1rh2w==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.1.tgz", + "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.227", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz", + "integrity": "sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-urlrewrite": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/express-urlrewrite/-/express-urlrewrite-1.4.0.tgz", + "integrity": "sha512-PI5h8JuzoweS26vFizwQl6UTF25CAHSggNv0J25Dn/IKZscJHWZzPrI5z2Y2jgOzIaw2qh8l6+/jUcig23Z2SA==", + "license": "MIT", + "dependencies": { + "debug": "*", + "path-to-regexp": "^1.0.3" + } + }, + "node_modules/express-urlrewrite/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-urlrewrite/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/express-urlrewrite/node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stringify": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stringify/-/fast-json-stringify-6.1.0.tgz", + "integrity": "sha512-bRnlgdhNd5sr3041TDg//jagICPZPwVjDK7I58BDnlB49l87wdYpYIO4zOr2lzHTah5tbbiBLyfPPeIUqHS/nQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.6.1.tgz", + "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-my-way": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/flv.js": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/flv.js/-/flv.js-1.6.2.tgz", + "integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==", + "license": "Apache-2.0", + "dependencies": { + "es6-promise": "^4.2.8", + "webworkify-webpack": "^2.1.5" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hls.js": { + "version": "1.6.13", + "resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.6.13.tgz", + "integrity": "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==", + "license": "Apache-2.0" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jiti": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.0.tgz", + "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "license": "MIT" + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "license": "MIT", + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-server": { + "version": "0.17.4", + "resolved": "https://registry.npmmirror.com/json-server/-/json-server-0.17.4.tgz", + "integrity": "sha512-bGBb0WtFuAKbgI7JV3A864irWnMZSvBYRJbohaOuatHwKSRFUfqtQlrYMrB6WbalXy/cJabyjlb7JkHli6dYjQ==", + "license": "MIT", + "dependencies": { + "body-parser": "^1.19.0", + "chalk": "^4.1.2", + "compression": "^1.7.4", + "connect-pause": "^0.1.1", + "cors": "^2.8.5", + "errorhandler": "^1.5.1", + "express": "^4.17.1", + "express-urlrewrite": "^1.4.0", + "json-parse-helpfulerror": "^1.0.3", + "lodash": "^4.17.21", + "lodash-id": "^0.14.1", + "lowdb": "^1.0.0", + "method-override": "^3.0.0", + "morgan": "^1.10.0", + "nanoid": "^3.1.23", + "please-upgrade-node": "^3.2.0", + "pluralize": "^8.0.0", + "server-destroy": "^1.0.1", + "yargs": "^17.0.1" + }, + "bin": { + "json-server": "lib/cli/bin.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-id": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/lodash-id/-/lodash-id-0.14.1.tgz", + "integrity": "sha512-ikQPBTiq/d5m6dfKQlFdIXFzvThPi2Be9/AHxktOnDSfSxE1j9ICbBT5Elk1ke7HSTgM38LHTpmJovo9/klnLg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "license": "MIT", + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-precision": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/number-precision/-/number-precision-1.6.0.tgz", + "integrity": "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/option-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/option-validator/-/option-validator-2.0.6.tgz", + "integrity": "sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.3" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/pino": { + "version": "9.12.0", + "resolved": "https://registry.npmmirror.com/pino/-/pino-9.12.0.tgz", + "integrity": "sha512-0Gd0OezGvqtqMwgYxpL7P0pSHHzTJ0Lx992h+mNlMtRVfNnqweWmf0JmRWk5gJzHalyd2mxTzKjhiNbGS2Ztfw==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/playwright": { + "version": "1.55.1", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.55.1.tgz", + "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.55.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.1", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.55.1.tgz", + "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.52.3", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.52.3.tgz", + "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.3", + "@rollup/rollup-android-arm64": "4.52.3", + "@rollup/rollup-darwin-arm64": "4.52.3", + "@rollup/rollup-darwin-x64": "4.52.3", + "@rollup/rollup-freebsd-arm64": "4.52.3", + "@rollup/rollup-freebsd-x64": "4.52.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", + "@rollup/rollup-linux-arm-musleabihf": "4.52.3", + "@rollup/rollup-linux-arm64-gnu": "4.52.3", + "@rollup/rollup-linux-arm64-musl": "4.52.3", + "@rollup/rollup-linux-loong64-gnu": "4.52.3", + "@rollup/rollup-linux-ppc64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-musl": "4.52.3", + "@rollup/rollup-linux-s390x-gnu": "4.52.3", + "@rollup/rollup-linux-x64-gnu": "4.52.3", + "@rollup/rollup-linux-x64-musl": "4.52.3", + "@rollup/rollup-openharmony-arm64": "4.52.3", + "@rollup/rollup-win32-arm64-msvc": "4.52.3", + "@rollup/rollup-win32-ia32-msvc": "4.52.3", + "@rollup/rollup-win32-x64-gnu": "4.52.3", + "@rollup/rollup-win32-x64-msvc": "4.52.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" } }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "node": ">=10" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" + "compute-scroll-into-view": "^1.0.20" } }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, + "node_modules/secure-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-4.0.0.tgz", + "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.8" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shaka-player": { + "version": "4.16.3", + "resolved": "https://registry.npmmirror.com/shaka-player/-/shaka-player-4.16.3.tgz", + "integrity": "sha512-KFvcg78uwoAyYJAlEa8y673XpPZeQCVG9L1ikoCSUMhCHeNNkNiG1FB4LZwGYN1IrwVEPH3IJVpWoB7RU4d0HA==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=8" } }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mlly": { - "version": "1.7.2", - "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.2.tgz", - "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==", + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.12.1", - "pathe": "^1.1.2", - "pkg-types": "^1.2.0", - "ufo": "^1.5.4" + "is-arrayish": "^0.3.1" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "node_modules/slow-redact": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/slow-redact/-/slow-redact-0.3.0.tgz", + "integrity": "sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA==", "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "dependencies": { + "atomic-sleep": "^1.0.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.8" } }, - "node_modules/pkg-types": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.2.1.tgz", - "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", - "dev": true, + "node_modules/steno": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/steno/-/steno-0.4.4.tgz", + "integrity": "sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==", "license": "MIT", "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.2", - "pathe": "^1.1.2" + "graceful-fs": "^4.1.3" } }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "node_modules/primevue": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/primevue/-/primevue-4.2.1.tgz", - "integrity": "sha512-cU9ZQVq9fitsQEIrfGeIl7xELBn61JCMxWkzcS9dkr165g29AvUrUNS9ufs1t2NoMJzE8VllwzweF/tSFAr2cw==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "@primeuix/styled": "^0.3.0", - "@primeuix/utils": "^0.3.0", - "@primevue/core": "4.2.1", - "@primevue/icons": "4.2.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=8.10.0" + "node": ">=8" } }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=8.6" + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=8" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/rollup": { - "version": "4.24.4", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.24.4.tgz", - "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", + "node_modules/tar": { + "version": "7.5.1", + "resolved": "https://registry.npmmirror.com/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.4", - "@rollup/rollup-android-arm64": "4.24.4", - "@rollup/rollup-darwin-arm64": "4.24.4", - "@rollup/rollup-darwin-x64": "4.24.4", - "@rollup/rollup-freebsd-arm64": "4.24.4", - "@rollup/rollup-freebsd-x64": "4.24.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.4", - "@rollup/rollup-linux-arm-musleabihf": "4.24.4", - "@rollup/rollup-linux-arm64-gnu": "4.24.4", - "@rollup/rollup-linux-arm64-musl": "4.24.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4", - "@rollup/rollup-linux-riscv64-gnu": "4.24.4", - "@rollup/rollup-linux-s390x-gnu": "4.24.4", - "@rollup/rollup-linux-x64-gnu": "4.24.4", - "@rollup/rollup-linux-x64-musl": "4.24.4", - "@rollup/rollup-win32-arm64-msvc": "4.24.4", - "@rollup/rollup-win32-ia32-msvc": "4.24.4", - "@rollup/rollup-win32-x64-msvc": "4.24.4", - "fsevents": "~2.3.2" + "node": ">=18" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "license": "MIT", "dependencies": { - "queue-microtask": "^1.2.2" + "real-require": "^0.2.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/to-regex-range": { @@ -1039,17 +4120,57 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmmirror.com/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true, "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unplugin": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.15.0.tgz", - "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==", + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", "dev": true, "license": "MIT", "dependencies": { @@ -1058,33 +4179,25 @@ }, "engines": { "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } } }, "node_modules/unplugin-vue-components": { - "version": "0.27.4", - "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz", - "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==", + "version": "0.27.5", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", + "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==", "dev": true, "license": "MIT", "dependencies": { "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.0", + "@rollup/pluginutils": "^5.1.3", "chokidar": "^3.6.0", - "debug": "^4.3.6", + "debug": "^4.3.7", "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.11", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", "minimatch": "^9.0.5", - "mlly": "^1.7.1", - "unplugin": "^1.12.1" + "mlly": "^1.7.3", + "unplugin": "^1.16.0" }, "engines": { "node": ">=14" @@ -1106,22 +4219,119 @@ } } }, + "node_modules/unplugin-vue-components/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v-viewer": { + "version": "3.0.22", + "resolved": "https://registry.npmmirror.com/v-viewer/-/v-viewer-3.0.22.tgz", + "integrity": "sha512-uYyP5FPT4K/Sd5D1mhB2HMVV8jnf6zYy2HD1PHCNAO6s2Iway+Wls60pwh7y4F3e2Nlc9549Pvy2HXaq8PKrAg==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21", + "viewerjs": "^1.11.6" + }, + "peerDependencies": { + "viewerjs": "^1.11.0", + "vue": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/viewerjs": { + "version": "1.11.7", + "resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.11.7.tgz", + "integrity": "sha512-0JuVqOmL5v1jmEAlG5EBDR3XquxY8DWFQbFMprOXgaBB0F7Q/X9xWdEaQc59D8xzwkdUgXEMSSknTpriq95igg==", + "license": "MIT" + }, "node_modules/vite": { - "version": "5.4.10", - "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.10.tgz", - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "version": "7.1.7", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1130,19 +4340,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -1163,20 +4379,26 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vue": { - "version": "3.5.12", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz", - "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", + "version": "3.5.22", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-sfc": "3.5.12", - "@vue/runtime-dom": "3.5.12", - "@vue/server-renderer": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" }, "peerDependencies": { "typescript": "*" @@ -1187,12 +4409,189 @@ } } }, + "node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-echarts": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/vue-echarts/-/vue-echarts-7.0.3.tgz", + "integrity": "sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==", + "license": "MIT", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/runtime-core": "^3.0.0", + "echarts": "^5.5.1", + "vue": "^2.7.0 || ^3.1.1" + }, + "peerDependenciesMeta": { + "@vue/runtime-core": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true, "license": "MIT" + }, + "node_modules/webworkify-webpack": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz", + "integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" } } } diff --git a/dashboard/package.json b/dashboard/package.json index 298fb43..64ece72 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,26 +1,58 @@ { - "name": "dashboard", + "name": "drplayer", "private": true, - "version": "0.0.0", + "version": "1.0.3 20251017", "type": "module", "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "build:root": "vite build --mode production.root", + "build:apps": "vite build --mode production.apps", + "build:binary": "node build-binary.js", + "build:binary:win": "node build-binary.js", + "build:binary:optimized": "node build-binary-optimized.js", + "preview": "vite preview", + "start": "node production-server.cjs", + "mock1": "json-server src/mock/data.json --host 127.0.0.1 --port 9978 --middlewares src/mock/middlewares.js", + "mock2": "json-server public/mock/data.json --host 127.0.0.1 --port 9978 --middlewares public/mock/middlewares.js" }, + "engines": { + "node": ">17 <23" + }, + "repository": "https://github.com/hjdhnx/DrPlayer.git", + "author": "晚风拂柳颜 <434857005@qq.com>", + "description": "道长修仙,法力无边。一统江湖,只需半年。\n君子袒蛋蛋,小人藏鸡鸡。", + "license": "MIT", "dependencies": { - "@primevue/themes": "^4.2.1", + "@fastify/static": "^6.12.0", + "artplayer": "^5.3.0", + "artplayer-plugin-danmuku": "^5.2.0", + "axios": "1.12.0", + "crypto-js": "^4.2.0", + "echarts": "^5.6.0", + "fastify": "^4.29.1", + "flv.js": "^1.6.2", + "hls.js": "^1.6.13", + "json-server": "^0.17.4", + "mpegts.js": "^1.8.0", "pinia": "^2.2.6", - "primeflex": "^3.3.1", - "primeicons": "^7.0.0", - "primevue": "^4.2.1", + "shaka-player": "^4.16.3", + "v-viewer": "^3.0.22", + "viewerjs": "^1.11.0", "vue": "^3.5.12", - "vue-router": "^4.4.5" + "vue-echarts": "^7.0.3", + "vue-router": "^4.4.5", + "vuedraggable": "^4.1.0" }, "devDependencies": { - "@primevue/auto-import-resolver": "^4.2.1", - "@vitejs/plugin-vue": "^5.1.4", + "@arco-design/web-vue": "^2.56.3", + "@playwright/test": "^1.55.1", + "@tailwindcss/postcss": "^4.1.13", + "@vitejs/plugin-vue": "^6.0.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.13", "unplugin-vue-components": "^0.27.4", - "vite": "^5.4.10" + "vite": "^7.1.7" } } diff --git a/dashboard/playwright.config.js b/dashboard/playwright.config.js new file mode 100644 index 0000000..c833bcc --- /dev/null +++ b/dashboard/playwright.config.js @@ -0,0 +1,28 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:5173', + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, +}); \ No newline at end of file diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml new file mode 100644 index 0000000..3ddbe9e --- /dev/null +++ b/dashboard/pnpm-lock.yaml @@ -0,0 +1,3512 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fastify/static': + specifier: ^6.12.0 + version: 6.12.0 + artplayer: + specifier: ^5.3.0 + version: 5.3.0 + artplayer-plugin-danmuku: + specifier: ^5.2.0 + version: 5.2.0 + axios: + specifier: 1.12.0 + version: 1.12.0 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + echarts: + specifier: ^5.6.0 + version: 5.6.0 + fastify: + specifier: ^4.29.1 + version: 4.29.1 + flv.js: + specifier: ^1.6.2 + version: 1.6.2 + hls.js: + specifier: ^1.6.13 + version: 1.6.13 + json-server: + specifier: ^0.17.4 + version: 0.17.4 + mpegts.js: + specifier: ^1.8.0 + version: 1.8.0 + pinia: + specifier: ^2.2.6 + version: 2.3.1(vue@3.5.22) + shaka-player: + specifier: ^4.16.3 + version: 4.16.3 + v-viewer: + specifier: ^3.0.22 + version: 3.0.22(viewerjs@1.11.7)(vue@3.5.22) + viewerjs: + specifier: ^1.11.0 + version: 1.11.7 + vue: + specifier: ^3.5.12 + version: 3.5.22 + vue-echarts: + specifier: ^7.0.3 + version: 7.0.3(@vue/runtime-core@3.5.22)(echarts@5.6.0)(vue@3.5.22) + vue-router: + specifier: ^4.4.5 + version: 4.5.1(vue@3.5.22) + vuedraggable: + specifier: ^4.1.0 + version: 4.1.0(vue@3.5.22) + devDependencies: + '@arco-design/web-vue': + specifier: ^2.56.3 + version: 2.57.0(vue@3.5.22) + '@playwright/test': + specifier: ^1.55.1 + version: 1.55.1 + '@tailwindcss/postcss': + specifier: ^4.1.13 + version: 4.1.14 + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1))(vue@3.5.22) + autoprefixer: + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.6) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^4.1.13 + version: 4.1.14 + unplugin-vue-components: + specifier: ^0.27.4 + version: 0.27.5(@babel/parser@7.28.4)(rollup@4.52.3)(vue@3.5.22) + vite: + specifier: ^7.1.7 + version: 7.1.9(jiti@2.6.1)(lightningcss@1.30.1) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@arco-design/color@0.4.0': + resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==} + + '@arco-design/web-vue@2.57.0': + resolution: {integrity: sha512-R5YReC3C2sG3Jv0+YuR3B7kzkq2KdhhQNCGXD8T11xAoa0zMt6SWTP1xJQOdZcM9du+q3z6tk5mRvh4qkieRJw==} + peerDependencies: + vue: ^3.1.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fastify/accept-negotiator@1.1.0': + resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} + engines: {node: '>=14'} + + '@fastify/ajv-compiler@3.6.0': + resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==} + + '@fastify/error@3.4.1': + resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} + + '@fastify/fast-json-stringify-compiler@4.3.0': + resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + + '@fastify/merge-json-schemas@0.1.1': + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + + '@fastify/send@2.1.0': + resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} + + '@fastify/static@6.12.0': + resolution: {integrity: sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@playwright/test@1.55.1': + resolution: {integrity: sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==} + engines: {node: '>=18'} + hasBin: true + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.52.3': + resolution: {integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.3': + resolution: {integrity: sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.3': + resolution: {integrity: sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.3': + resolution: {integrity: sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.3': + resolution: {integrity: sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.3': + resolution: {integrity: sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.52.3': + resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.52.3': + resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.52.3': + resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.52.3': + resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + resolution: {integrity: sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + resolution: {integrity: sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.3': + resolution: {integrity: sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.3': + resolution: {integrity: sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==} + cpu: [x64] + os: [win32] + + '@tailwindcss/node@4.1.14': + resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} + + '@tailwindcss/oxide-android-arm64@4.1.14': + resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.14': + resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.14': + resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.14': + resolution: {integrity: sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.22': + resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + + '@vue/compiler-dom@3.5.22': + resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + + '@vue/compiler-sfc@3.5.22': + resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} + + '@vue/compiler-ssr@3.5.22': + resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.22': + resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} + + '@vue/runtime-core@3.5.22': + resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==} + + '@vue/runtime-dom@3.5.22': + resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==} + + '@vue/server-renderer@3.5.22': + resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==} + peerDependencies: + vue: 3.5.22 + + '@vue/shared@3.5.22': + resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + artplayer-plugin-danmuku@5.2.0: + resolution: {integrity: sha512-iHHSGEc0J+gl7imX4nmLqqC7LXmv8xeGf2lUevhhyUCoiCRZplecpKQICHjErVb1Yqyp1xCy6Sp8eYN+EpFWFw==} + + artplayer@5.3.0: + resolution: {integrity: sha512-yExO39MpEg4P+bxgChxx1eJfiUPE4q1QQRLCmqGhlsj+ANuaoEkR8hF93LdI5ZyrAcIbJkuEndxEiUoKobifDw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + avvio@8.4.0: + resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==} + + axios@1.12.0: + resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==} + + b-tween@0.3.3: + resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==} + + b-validate@1.5.3: + resolution: {integrity: sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.8.10: + resolution: {integrity: sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==} + hasBin: true + + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.26.3: + resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001746: + resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + connect-pause@0.1.1: + resolution: {integrity: sha512-a1gSWQBQD73krFXdUEYJom2RTFrWUL3YvXDCRkyv//GVXc79cdW9MngtRuN9ih4FDKBtfJAJId+BbDuX+1rh2w==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.1: + resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.229: + resolution: {integrity: sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + errorhandler@1.5.1: + resolution: {integrity: sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express-urlrewrite@1.4.0: + resolution: {integrity: sha512-PI5h8JuzoweS26vFizwQl6UTF25CAHSggNv0J25Dn/IKZscJHWZzPrI5z2Y2jgOzIaw2qh8l6+/jUcig23Z2SA==} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + fast-content-type-parse@1.1.0: + resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stringify@5.16.1: + resolution: {integrity: sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-uri@2.4.0: + resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} + + fastify@4.29.1: + resolution: {integrity: sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-my-way@8.2.2: + resolution: {integrity: sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==} + engines: {node: '>=14'} + + flv.js@1.6.2: + resolution: {integrity: sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hls.js@1.6.13: + resolution: {integrity: sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + json-parse-helpfulerror@1.0.3: + resolution: {integrity: sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==} + + json-schema-ref-resolver@1.0.1: + resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-server@0.17.4: + resolution: {integrity: sha512-bGBb0WtFuAKbgI7JV3A864irWnMZSvBYRJbohaOuatHwKSRFUfqtQlrYMrB6WbalXy/cJabyjlb7JkHli6dYjQ==} + engines: {node: '>=12'} + hasBin: true + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + light-my-request@5.14.0: + resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-id@0.14.1: + resolution: {integrity: sha512-ikQPBTiq/d5m6dfKQlFdIXFzvThPi2Be9/AHxktOnDSfSxE1j9ICbBT5Elk1ke7HSTgM38LHTpmJovo9/klnLg==} + engines: {node: '>= 4'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lowdb@1.0.0: + resolution: {integrity: sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==} + engines: {node: '>=4'} + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + method-override@3.0.0: + resolution: {integrity: sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==} + engines: {node: '>= 0.10'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + morgan@1.10.1: + resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} + engines: {node: '>= 0.8.0'} + + mpegts.js@1.8.0: + resolution: {integrity: sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + node-releases@2.0.21: + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + number-precision@1.6.0: + resolution: {integrity: sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + option-validator@2.0.6: + resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-to-regexp@1.9.0: + resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.13.0: + resolution: {integrity: sha512-SpTXQhkQXekIKEe7c887S3lk3v90Q+/HVRZVyNAhe98PQc++6I5ec/R0pciH8/CciXjCoVZIZfRNicbC6KZgnw==} + hasBin: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + playwright-core@1.55.1: + resolution: {integrity: sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.55.1: + resolution: {integrity: sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==} + engines: {node: '>=18'} + hasBin: true + + please-upgrade-node@3.2.0: + resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + ret@0.4.3: + resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.52.3: + resolution: {integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@3.1.0: + resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + server-destroy@1.0.1: + resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shaka-player@4.16.3: + resolution: {integrity: sha512-KFvcg78uwoAyYJAlEa8y673XpPZeQCVG9L1ikoCSUMhCHeNNkNiG1FB4LZwGYN1IrwVEPH3IJVpWoB7RU4d0HA==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + slow-redact@0.3.0: + resolution: {integrity: sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA==} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + steno@0.4.4: + resolution: {integrity: sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tailwindcss@4.1.14: + resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + engines: {node: '>=18'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unplugin-vue-components@0.27.5: + resolution: {integrity: sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v-viewer@3.0.22: + resolution: {integrity: sha512-uYyP5FPT4K/Sd5D1mhB2HMVV8jnf6zYy2HD1PHCNAO6s2Iway+Wls60pwh7y4F3e2Nlc9549Pvy2HXaq8PKrAg==} + peerDependencies: + viewerjs: ^1.11.0 + vue: ^3.0.0 + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + viewerjs@1.11.7: + resolution: {integrity: sha512-0JuVqOmL5v1jmEAlG5EBDR3XquxY8DWFQbFMprOXgaBB0F7Q/X9xWdEaQc59D8xzwkdUgXEMSSknTpriq95igg==} + + vite@7.1.9: + resolution: {integrity: sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vue-demi@0.13.11: + resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-echarts@7.0.3: + resolution: {integrity: sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==} + peerDependencies: + '@vue/runtime-core': ^3.0.0 + echarts: ^5.5.1 + vue: ^2.7.0 || ^3.1.1 + peerDependenciesMeta: + '@vue/runtime-core': + optional: true + + vue-router@4.5.1: + resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} + peerDependencies: + vue: ^3.2.0 + + vue@3.5.22: + resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable@4.1.0: + resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} + peerDependencies: + vue: ^3.0.1 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + webworkify-webpack@2.1.5: + resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==} + + webworkify-webpack@https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef: + resolution: {tarball: https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef} + version: 2.1.5 + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@antfu/utils@0.7.10': {} + + '@arco-design/color@0.4.0': + dependencies: + color: 3.2.1 + + '@arco-design/web-vue@2.57.0(vue@3.5.22)': + dependencies: + '@arco-design/color': 0.4.0 + b-tween: 0.3.3 + b-validate: 1.5.3 + compute-scroll-into-view: 1.0.20 + dayjs: 1.11.18 + number-precision: 1.6.0 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + vue: 3.5.22 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@fastify/accept-negotiator@1.1.0': {} + + '@fastify/ajv-compiler@3.6.0': + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + fast-uri: 2.4.0 + + '@fastify/error@3.4.1': {} + + '@fastify/fast-json-stringify-compiler@4.3.0': + dependencies: + fast-json-stringify: 5.16.1 + + '@fastify/merge-json-schemas@0.1.1': + dependencies: + fast-deep-equal: 3.1.3 + + '@fastify/send@2.1.0': + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + + '@fastify/static@6.12.0': + dependencies: + '@fastify/accept-negotiator': 1.1.0 + '@fastify/send': 2.1.0 + content-disposition: 0.5.4 + fastify-plugin: 4.5.1 + glob: 8.1.0 + p-limit: 3.1.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lukeed/ms@2.0.2': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@playwright/test@1.55.1': + dependencies: + playwright: 1.55.1 + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/pluginutils@5.3.0(rollup@4.52.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.3 + + '@rollup/rollup-android-arm-eabi@4.52.3': + optional: true + + '@rollup/rollup-android-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-x64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.3': + optional: true + + '@tailwindcss/node@4.1.14': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.14 + + '@tailwindcss/oxide-android-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide@4.1.14': + dependencies: + detect-libc: 2.1.1 + tar: 7.5.1 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-x64': 4.1.14 + '@tailwindcss/oxide-freebsd-x64': 4.1.14 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-x64-musl': 4.1.14 + '@tailwindcss/oxide-wasm32-wasi': 4.1.14 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 + + '@tailwindcss/postcss@4.1.14': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.14 + '@tailwindcss/oxide': 4.1.14 + postcss: 8.5.6 + tailwindcss: 4.1.14 + + '@types/estree@1.0.8': {} + + '@vitejs/plugin-vue@6.0.1(vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1))(vue@3.5.22)': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.9(jiti@2.6.1)(lightningcss@1.30.1) + vue: 3.5.22 + + '@vue/compiler-core@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.22 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.22': + dependencies: + '@vue/compiler-core': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-sfc@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/compiler-core': 3.5.22 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + estree-walker: 2.0.2 + magic-string: 0.30.19 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.22': + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.22': + dependencies: + '@vue/shared': 3.5.22 + + '@vue/runtime-core@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/runtime-dom@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/runtime-core': 3.5.22 + '@vue/shared': 3.5.22 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.22(vue@3.5.22)': + dependencies: + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + vue: 3.5.22 + + '@vue/shared@3.5.22': {} + + abstract-logging@2.0.1: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn@8.15.0: {} + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-flatten@1.1.1: {} + + artplayer-plugin-danmuku@5.2.0: {} + + artplayer@5.3.0: + dependencies: + option-validator: 2.0.6 + + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + + autoprefixer@10.4.21(postcss@8.5.6): + dependencies: + browserslist: 4.26.3 + caniuse-lite: 1.0.30001746 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + avvio@8.4.0: + dependencies: + '@fastify/error': 3.4.1 + fastq: 1.19.1 + + axios@1.12.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + b-tween@0.3.3: {} + + b-validate@1.5.3: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.8.10: {} + + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + + binary-extensions@2.3.0: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.26.3: + dependencies: + baseline-browser-mapping: 2.8.10 + caniuse-lite: 1.0.30001746 + electron-to-chromium: 1.5.229 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.3) + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001746: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + compute-scroll-into-view@1.0.20: {} + + confbox@0.1.8: {} + + connect-pause@0.1.1: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + crypto-js@4.2.0: {} + + csstype@3.1.3: {} + + dayjs@1.11.18: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.1.0: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.229: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + errorhandler@1.5.1: + dependencies: + accepts: 1.3.8 + escape-html: 1.0.3 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es6-promise@4.2.8: {} + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + estree-walker@2.0.2: {} + + etag@1.8.1: {} + + express-urlrewrite@1.4.0: + dependencies: + debug: 4.4.3 + path-to-regexp: 1.9.0 + transitivePeerDependencies: + - supports-color + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-content-type-parse@1.1.0: {} + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stringify@5.16.1: + dependencies: + '@fastify/merge-json-schemas': 0.1.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-deep-equal: 3.1.3 + fast-uri: 2.4.0 + json-schema-ref-resolver: 1.0.1 + rfdc: 1.4.1 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-uri@2.4.0: {} + + fast-uri@3.1.0: {} + + fastify-plugin@4.5.1: {} + + fastify@4.29.1: + dependencies: + '@fastify/ajv-compiler': 3.6.0 + '@fastify/error': 3.4.1 + '@fastify/fast-json-stringify-compiler': 4.3.0 + abstract-logging: 2.0.1 + avvio: 8.4.0 + fast-content-type-parse: 1.1.0 + fast-json-stringify: 5.16.1 + find-my-way: 8.2.2 + light-my-request: 5.14.0 + pino: 9.13.0 + process-warning: 3.0.0 + proxy-addr: 2.0.7 + rfdc: 1.4.1 + secure-json-parse: 2.7.0 + semver: 7.7.2 + toad-cache: 3.7.0 + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-my-way@8.2.2: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 3.1.0 + + flv.js@1.6.2: + dependencies: + es6-promise: 4.2.8 + webworkify-webpack: 2.1.5 + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fraction.js@4.3.7: {} + + fresh@0.5.2: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hls.js@1.6.13: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.3.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-promise@2.2.2: {} + + isarray@0.0.1: {} + + jiti@2.6.1: {} + + jju@1.4.0: {} + + json-parse-helpfulerror@1.0.3: + dependencies: + jju: 1.4.0 + + json-schema-ref-resolver@1.0.1: + dependencies: + fast-deep-equal: 3.1.3 + + json-schema-traverse@1.0.0: {} + + json-server@0.17.4: + dependencies: + body-parser: 1.20.3 + chalk: 4.1.2 + compression: 1.8.1 + connect-pause: 0.1.1 + cors: 2.8.5 + errorhandler: 1.5.1 + express: 4.21.2 + express-urlrewrite: 1.4.0 + json-parse-helpfulerror: 1.0.3 + lodash: 4.17.21 + lodash-id: 0.14.1 + lowdb: 1.0.0 + method-override: 3.0.0 + morgan: 1.10.1 + nanoid: 3.3.11 + please-upgrade-node: 3.2.0 + pluralize: 8.0.0 + server-destroy: 1.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + kind-of@6.0.3: {} + + light-my-request@5.14.0: + dependencies: + cookie: 0.7.1 + process-warning: 3.0.0 + set-cookie-parser: 2.7.1 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.1.1 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + local-pkg@0.5.1: + dependencies: + mlly: 1.8.0 + pkg-types: 1.3.1 + + lodash-es@4.17.21: {} + + lodash-id@0.14.1: {} + + lodash@4.17.21: {} + + lowdb@1.0.0: + dependencies: + graceful-fs: 4.2.11 + is-promise: 2.2.2 + lodash: 4.17.21 + pify: 3.0.0 + steno: 0.4.4 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + merge2@1.4.1: {} + + method-override@3.0.0: + dependencies: + debug: 3.1.0 + methods: 1.1.2 + parseurl: 1.3.3 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@3.0.0: {} + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + morgan@1.10.1: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.1.0 + transitivePeerDependencies: + - supports-color + + mpegts.js@1.8.0: + dependencies: + es6-promise: 4.2.8 + webworkify-webpack: https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef + + ms@2.0.0: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + node-releases@2.0.21: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + number-precision@1.6.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + option-validator@2.0.6: + dependencies: + kind-of: 6.0.3 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + parseurl@1.3.3: {} + + path-to-regexp@0.1.12: {} + + path-to-regexp@1.9.0: + dependencies: + isarray: 0.0.1 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@3.0.0: {} + + pinia@2.3.1(vue@3.5.22): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.22 + vue-demi: 0.14.10(vue@3.5.22) + transitivePeerDependencies: + - '@vue/composition-api' + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.13.0: + dependencies: + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + slow-redact: 0.3.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + playwright-core@1.55.1: {} + + playwright@1.55.1: + dependencies: + playwright-core: 1.55.1 + optionalDependencies: + fsevents: 2.3.2 + + please-upgrade-node@3.2.0: + dependencies: + semver-compare: 1.0.0 + + pluralize@8.0.0: {} + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + process-warning@3.0.0: {} + + process-warning@5.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + real-require@0.2.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resize-observer-polyfill@1.5.1: {} + + ret@0.4.3: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.52.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.3 + '@rollup/rollup-android-arm64': 4.52.3 + '@rollup/rollup-darwin-arm64': 4.52.3 + '@rollup/rollup-darwin-x64': 4.52.3 + '@rollup/rollup-freebsd-arm64': 4.52.3 + '@rollup/rollup-freebsd-x64': 4.52.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.3 + '@rollup/rollup-linux-arm-musleabihf': 4.52.3 + '@rollup/rollup-linux-arm64-gnu': 4.52.3 + '@rollup/rollup-linux-arm64-musl': 4.52.3 + '@rollup/rollup-linux-loong64-gnu': 4.52.3 + '@rollup/rollup-linux-ppc64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-musl': 4.52.3 + '@rollup/rollup-linux-s390x-gnu': 4.52.3 + '@rollup/rollup-linux-x64-gnu': 4.52.3 + '@rollup/rollup-linux-x64-musl': 4.52.3 + '@rollup/rollup-openharmony-arm64': 4.52.3 + '@rollup/rollup-win32-arm64-msvc': 4.52.3 + '@rollup/rollup-win32-ia32-msvc': 4.52.3 + '@rollup/rollup-win32-x64-gnu': 4.52.3 + '@rollup/rollup-win32-x64-msvc': 4.52.3 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex2@3.1.0: + dependencies: + ret: 0.4.3 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + secure-json-parse@2.7.0: {} + + semver-compare@1.0.0: {} + + semver@7.7.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + server-destroy@1.0.1: {} + + set-cookie-parser@2.7.1: {} + + setprototypeof@1.2.0: {} + + shaka-player@4.16.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + slow-redact@0.3.0: {} + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + sortablejs@1.14.0: {} + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + statuses@2.0.1: {} + + steno@0.4.4: + dependencies: + graceful-fs: 4.2.11 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tailwindcss@4.1.14: {} + + tapable@2.3.0: {} + + tar@7.5.1: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toad-cache@3.7.0: {} + + toidentifier@1.0.1: {} + + tslib@2.3.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + ufo@1.6.1: {} + + unpipe@1.0.0: {} + + unplugin-vue-components@0.27.5(@babel/parser@7.28.4)(rollup@4.52.3)(vue@3.5.22): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.3.0(rollup@4.52.3) + chokidar: 3.6.0 + debug: 4.4.3 + fast-glob: 3.3.3 + local-pkg: 0.5.1 + magic-string: 0.30.19 + minimatch: 9.0.5 + mlly: 1.8.0 + unplugin: 1.16.1 + vue: 3.5.22 + optionalDependencies: + '@babel/parser': 7.28.4 + transitivePeerDependencies: + - rollup + - supports-color + + unplugin@1.16.1: + dependencies: + acorn: 8.15.0 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.3(browserslist@4.26.3): + dependencies: + browserslist: 4.26.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + utils-merge@1.0.1: {} + + v-viewer@3.0.22(viewerjs@1.11.7)(vue@3.5.22): + dependencies: + lodash-es: 4.17.21 + viewerjs: 1.11.7 + vue: 3.5.22 + + vary@1.1.2: {} + + viewerjs@1.11.7: {} + + vite@7.1.9(jiti@2.6.1)(lightningcss@1.30.1): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.1 + + vue-demi@0.13.11(vue@3.5.22): + dependencies: + vue: 3.5.22 + + vue-demi@0.14.10(vue@3.5.22): + dependencies: + vue: 3.5.22 + + vue-echarts@7.0.3(@vue/runtime-core@3.5.22)(echarts@5.6.0)(vue@3.5.22): + dependencies: + echarts: 5.6.0 + vue: 3.5.22 + vue-demi: 0.13.11(vue@3.5.22) + optionalDependencies: + '@vue/runtime-core': 3.5.22 + transitivePeerDependencies: + - '@vue/composition-api' + + vue-router@4.5.1(vue@3.5.22): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.22 + + vue@3.5.22: + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-sfc': 3.5.22 + '@vue/runtime-dom': 3.5.22 + '@vue/server-renderer': 3.5.22(vue@3.5.22) + '@vue/shared': 3.5.22 + + vuedraggable@4.1.0(vue@3.5.22): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.22 + + webpack-virtual-modules@0.6.2: {} + + webworkify-webpack@2.1.5: {} + + webworkify-webpack@https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + + yallist@5.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/dashboard/pnpm-workspace.yaml b/dashboard/pnpm-workspace.yaml new file mode 100644 index 0000000..b6ac3d6 --- /dev/null +++ b/dashboard/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +# pnpm workspace configuration +packages: + - '.' diff --git a/dashboard/postcss.config.js b/dashboard/postcss.config.js new file mode 100644 index 0000000..af9d8dc --- /dev/null +++ b/dashboard/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/dashboard/production-server.cjs b/dashboard/production-server.cjs new file mode 100644 index 0000000..8244449 --- /dev/null +++ b/dashboard/production-server.cjs @@ -0,0 +1,400 @@ +#!/usr/bin/env node +/** + * 生产服务器 - DrPlayer Dashboard (CommonJS版本,用于PKG打包) + */ + +const Fastify = require('fastify'); +const fastifyStatic = require('@fastify/static'); +const path = require('path'); +const fs = require('fs/promises'); +const { execSync } = require('child_process'); +const net = require('net'); + +// 内联 SPA 路由功能 (避免ES6模块依赖) +async function addSPARoutes(fastify, options) { + const spaApps = options.spaApps || ['drplayer']; + + for (const appName of spaApps) { + fastify.get(`/apps/${appName}`, async (request, reply) => { + return reply.redirect(301, `/apps/${appName}/`); + }); + + fastify.get(`/apps/${appName}/`, async (request, reply) => { + const indexPath = path.join(options.appsDir, appName, 'index.html'); + + try { + const indexContent = await fs.readFile(indexPath, 'utf8'); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); + } catch (error) { + return reply.code(404).send({ error: `${appName} application not found` }); + } + }); + } + + fastify.setNotFoundHandler(async (request, reply) => { + const url = request.url; + + for (const appName of spaApps) { + const appPrefix = `/apps/${appName}/`; + + if (url.startsWith(appPrefix)) { + const urlPath = url.replace(appPrefix, ''); + const hasExtension = /\.[a-zA-Z0-9]+(\?.*)?$/.test(urlPath); + + if (!hasExtension) { + const indexPath = path.join(options.appsDir, appName, 'index.html'); + + try { + const indexContent = await fs.readFile(indexPath, 'utf8'); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); + } catch (error) { + return reply.code(404).send({ error: `${appName} application not found` }); + } + } + } + } + + return reply.code(404).send({ error: 'Not Found' }); + }); +} + +// PKG 环境下的路径处理 +const isPkg = typeof process.pkg !== 'undefined'; + +// 在CommonJS中,__filename和__dirname是全局可用的 +// 在PKG环境中需要重新定义 +let currentFilename, currentDirname; + +if (isPkg) { + currentFilename = process.execPath; + currentDirname = path.dirname(process.execPath); +} else { + currentFilename = __filename; + currentDirname = __dirname; +} + +// 检查端口是否可用 +function checkPortAvailable(port) { + return new Promise((resolve) => { + const server = net.createServer(); + let resolved = false; + + // 设置超时机制 + const timeout = setTimeout(() => { + if (!resolved) { + resolved = true; + server.close(); + resolve(false); + } + }, 1000); + + server.listen(port, '0.0.0.0', () => { + if (!resolved) { + resolved = true; + clearTimeout(timeout); + server.close(() => { + resolve(true); + }); + } + }); + + server.on('error', (err) => { + if (!resolved) { + resolved = true; + clearTimeout(timeout); + resolve(false); + } + }); + }); +} + +// 查找可用端口 +async function findAvailablePort(startPort = 9978, maxAttempts = 100) { + for (let i = 0; i < maxAttempts; i++) { + const port = startPort + i; + const isAvailable = await checkPortAvailable(port); + if (isAvailable) { + return port; + } + console.log(`端口 ${port} 已被占用,尝试下一个端口...`); + } + throw new Error(`无法找到可用端口,已尝试 ${startPort} 到 ${startPort + maxAttempts - 1}`); +} + +// PKG 环境下的工作目录处理 +let workDir; +if (isPkg) { + workDir = process.cwd(); + console.log('PKG 环境检测到,工作目录:', workDir); + console.log('可执行文件路径:', process.execPath); +} else { + workDir = currentDirname; +} + +// 构建和部署函数 +async function buildAndDeploy() { + console.log('🔨 开始构建应用...'); + + try { + if (!isPkg) { + try { + execSync('pnpm build:apps', { stdio: 'inherit', cwd: currentDirname }); + console.log('✅ 构建完成'); + } catch (buildError) { + console.warn('⚠️ 构建命令执行失败,可能是在打包环境中运行:', buildError.message); + console.log('📦 跳过构建步骤,使用预构建的文件'); + } + } else { + console.log('📦 检测到pkg环境,跳过构建步骤'); + } + + const appsDir = path.join(workDir, 'apps'); + const drplayerDir = path.join(appsDir, 'drplayer'); + + await fs.mkdir(drplayerDir, { recursive: true }); + console.log('📁 创建apps目录'); + + const distDir = path.join(workDir, 'dist'); + try { + await fs.access(distDir); + await copyDirectory(distDir, drplayerDir); + console.log('📋 复制文件到apps/drplayer'); + } catch (error) { + console.warn('⚠️ dist目录不存在,跳过文件复制'); + if (isPkg) { + console.log('📦 pkg环境中,请确保静态文件已正确打包'); + } + } + + } catch (error) { + console.error('❌ 构建失败:', error.message); + if (typeof process.pkg === 'undefined') { + process.exit(1); + } else { + console.log('📦 pkg环境中继续运行...'); + } + } +} + +// 递归复制目录 +async function copyDirectory(src, dest) { + await fs.mkdir(dest, { recursive: true }); + const entries = await fs.readdir(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + await copyDirectory(srcPath, destPath); + } else { + await fs.copyFile(srcPath, destPath); + } + } +} + +// 主函数 +async function main() { + const fastify = Fastify({ + logger: false + }); + + let PORT = 9978; + + // PKG环境中的静态文件路径处理 + let appsDir; + if (isPkg) { + // PKG环境中,静态文件在工作目录下的apps目录 + appsDir = path.join(workDir, 'apps'); + console.log('📦 PKG环境中使用工作目录的静态文件路径:', appsDir); + } else { + // 开发环境中使用工作目录 + appsDir = path.join(workDir, 'apps'); + console.log('🔧 开发环境中使用工作目录:', appsDir); + } + + const options = { + appsDir: appsDir, + port: PORT + }; + + // 注册静态文件服务 + await fastify.register(fastifyStatic, { + root: options.appsDir, + prefix: '/apps/', + decorateReply: false, + }); + + // 注册SPA路由支持 + await fastify.register(addSPARoutes, { + appsDir: options.appsDir, + spaApps: ['drplayer'] + }); + + // 根路径 - 显示应用列表 + fastify.get('/', async (request, reply) => { + let version = '1.0.0'; + try { + const packageJsonPath = path.join(currentDirname, 'package.json'); + const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8'); + const packageJson = JSON.parse(packageJsonContent); + version = packageJson.version || '1.0.0'; + } catch (error) { + console.warn('无法读取package.json版本信息:', error.message); + } + + const html = ` + + + + + + DrPlayer Dashboard + + + +
+

🎬 DrPlayer Dashboard

+ +
+ ✅ 服务器运行正常 - 端口: ${PORT} +
+ + + + +
+ +`; + + return reply.type('text/html').send(html); + }); + + // 健康检查 + fastify.get('/health', async (request, reply) => { + return {status: 'ok', timestamp: new Date().toISOString()}; + }); + + // 启动服务器 + const start = async () => { + try { + await buildAndDeploy(); + + console.log(`🔍 正在查找可用端口,起始端口: ${PORT}`); + const availablePort = await findAvailablePort(PORT); + PORT = availablePort; + options.port = PORT; + + console.log(`✅ 找到可用端口: ${PORT}`); + + await fastify.listen({ port: PORT, host: '0.0.0.0' }); + console.log(`🚀 生产服务器启动成功!`); + console.log(`📱 访问地址: http://localhost:${PORT}/apps/drplayer/`); + console.log(`🔍 健康检查: http://localhost:${PORT}/health`); + console.log(`📦 运行环境: ${isPkg ? 'PKG二进制' : '开发环境'}`); + + if (isPkg) { + console.log(`📁 工作目录: ${workDir}`); + console.log(`📂 应用目录: ${options.appsDir}`); + } + + } catch (err) { + console.error('❌ 服务器启动失败:', err.message); + fastify.log.error(err); + process.exit(1); + } + }; + + // 优雅关闭 + process.on('SIGINT', async () => { + console.log('\n🛑 正在关闭服务器...'); + await fastify.close(); + process.exit(0); + }); + + await start(); +} + +// 启动应用 +main().catch(console.error); \ No newline at end of file diff --git a/dashboard/public/_redirects b/dashboard/public/_redirects new file mode 100644 index 0000000..f824337 --- /dev/null +++ b/dashboard/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/dashboard/public/default-poster.svg b/dashboard/public/default-poster.svg new file mode 100644 index 0000000..8e76012 --- /dev/null +++ b/dashboard/public/default-poster.svg @@ -0,0 +1,6 @@ + + + + + 暂无图片 + \ No newline at end of file diff --git a/dashboard/public/lives.jpg b/dashboard/public/lives.jpg new file mode 100644 index 0000000..f28eeaa Binary files /dev/null and b/dashboard/public/lives.jpg differ diff --git a/dashboard/public/mock/data.json b/dashboard/public/mock/data.json new file mode 100644 index 0000000..37551e1 --- /dev/null +++ b/dashboard/public/mock/data.json @@ -0,0 +1,6168 @@ +{ + "config": { + "wallpaper":"https://tuapi.eees.cc/api.php?category=fengjing&type=302", + "hipy_sites_count":408, + "mode":1, + "spider":"https://hipy.trylyingto.me/files/jar/pg.jar?md5=7633f8ea346c082b7aa163be58aed023", + "homepage":"https://github.com/hjdhnx/hipy-server", + "homeLogo":"https://hipy.trylyingto.me/static/img/logo500x200-1.png", + "sites":[ + { + "key":"hipy_py_cntv央视", + "name":"cntv央视(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/cntv央视.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":0, + "ext":"https://hipy.trylyingto.me/files/hipy/cntv央视.json" + }, + { + "key":"hipy_py_两个BT", + "name":"两个BT(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/两个BT.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":9, + "ext":"https://hipy.trylyingto.me/files/hipy/两个BT.json" + }, + { + "key":"hipy_py_哔滴影视", + "name":"哔滴影视(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/哔滴影视.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":10, + "ext":"https://hipy.trylyingto.me/files/hipy/jars/bidi.jar" + }, + { + "key":"hipy_py_喵次元", + "name":"喵次元(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/喵次元.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":11, + "ext":"" + }, + { + "key":"hipy_py_新浪资源", + "name":"新浪资源(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/新浪资源.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":12, + "ext":"" + }, + { + "key":"hipy_py_樱花动漫", + "name":"樱花动漫(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/樱花动漫.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":14, + "ext":"https://jihulab.com/qiaoji/open/-/raw/main/yinghua" + }, + { + "key":"hipy_js_在线之家", + "name":"在线之家(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":54, + "ext":"https://hipy.trylyingto.me/files/drpy_js/在线之家.js" + }, + { + "key":"hipy_js_555影视[飞]", + "name":"555影视[飞](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":56, + "ext":"https://hipy.trylyingto.me/files/drpy_js/555影视[飞].js" + }, + { + "key":"hipy_js_freeok", + "name":"freeok(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":120, + "ext":"https://hipy.trylyingto.me/files/drpy_js/freeok.js" + }, + { + "key":"hipy_js_厂长资源", + "name":"厂长资源(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":121, + "ext":"https://hipy.trylyingto.me/files/drpy_js/厂长资源.js" + }, + { + "key":"hipy_js_耐看", + "name":"耐看(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":122, + "ext":"https://hipy.trylyingto.me/files/drpy_js/耐看.js" + }, + { + "key":"hipy_py_厂长资源", + "name":"厂长资源(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/厂长资源.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":129, + "ext":"" + }, + { + "key":"hipy_py_在线之家", + "name":"在线之家(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/在线之家.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":130, + "ext":"" + }, + { + "key":"hipy_js_白嫖者联盟", + "name":"白嫖者联盟(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":147, + "ext":"https://hipy.trylyingto.me/files/drpy_js/白嫖者联盟.js" + }, + { + "key":"hipy_js_大米星球", + "name":"大米星球(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":149, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大米星球.js" + }, + { + "key":"hipy_js_6V新版[磁]", + "name":"6V新版[磁](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":150, + "ext":"https://hipy.trylyingto.me/files/drpy_js/6V新版[磁].js" + }, + { + "key":"hipy_js_777影视", + "name":"777影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":152, + "ext":"https://hipy.trylyingto.me/files/drpy_js/777影视.js" + }, + { + "key":"hipy_js_榜一短剧", + "name":"榜一短剧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":154, + "ext":"https://hipy.trylyingto.me/files/drpy_js/榜一短剧.js" + }, + { + "key":"hipy_js_大米星球[V2]", + "name":"大米星球[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":155, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大米星球[V2].js" + }, + { + "key":"hipy_js_voflix", + "name":"voflix(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":156, + "ext":"https://hipy.trylyingto.me/files/drpy_js/voflix.js" + }, + { + "key":"hipy_js_TVB云播", + "name":"TVB云播(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":192, + "ext":"https://hipy.trylyingto.me/files/drpy_js/TVB云播.js" + }, + { + "key":"hipy_js_侠客影视", + "name":"侠客影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":194, + "ext":"https://hipy.trylyingto.me/files/drpy_js/侠客影视.js" + }, + { + "key":"hipy_js_七年影视", + "name":"七年影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":196, + "ext":"https://hipy.trylyingto.me/files/drpy_js/七年影视.js" + }, + { + "key":"hipy_js_磁力熊[磁]", + "name":"磁力熊[磁](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":197, + "ext":"https://hipy.trylyingto.me/files/drpy_js/磁力熊[磁].js" + }, + { + "key":"hipy_js_一起看", + "name":"一起看(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":211, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一起看.js" + }, + { + "key":"hipy_js_可达影视", + "name":"可达影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":212, + "ext":"https://hipy.trylyingto.me/files/drpy_js/可达影视.js" + }, + { + "key":"hipy_js_千神影视", + "name":"千神影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":213, + "ext":"https://hipy.trylyingto.me/files/drpy_js/千神影视.js" + }, + { + "key":"hipy_js_海外剧汇", + "name":"海外剧汇(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":214, + "ext":"https://hipy.trylyingto.me/files/drpy_js/海外剧汇.js" + }, + { + "key":"hipy_js_美剧星球", + "name":"美剧星球(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":215, + "ext":"https://hipy.trylyingto.me/files/drpy_js/美剧星球.js" + }, + { + "key":"hipy_js_橙汁影视", + "name":"橙汁影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":216, + "ext":"https://hipy.trylyingto.me/files/drpy_js/橙汁影视.js" + }, + { + "key":"hipy_js_掌心世界", + "name":"掌心世界(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":221, + "ext":"https://hipy.trylyingto.me/files/drpy_js/掌心世界.js" + }, + { + "key":"hipy_js_宝片视频", + "name":"宝片视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":223, + "ext":"https://hipy.trylyingto.me/files/drpy_js/宝片视频.js" + }, + { + "key":"hipy_js_易看影视", + "name":"易看影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":224, + "ext":"https://hipy.trylyingto.me/files/drpy_js/易看影视.js" + }, + { + "key":"hipy_js_新茶杯狐", + "name":"新茶杯狐(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":225, + "ext":"https://hipy.trylyingto.me/files/drpy_js/新茶杯狐.js" + }, + { + "key":"hipy_js_看57", + "name":"看57(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":226, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看57.js" + }, + { + "key":"hipy_js_神马电影[搜]", + "name":"神马电影[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":227, + "ext":"https://hipy.trylyingto.me/files/drpy_js/神马电影[搜].js" + }, + { + "key":"hipy_js_极客资源[资]", + "name":"极客资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":228, + "ext":"https://hipy.trylyingto.me/files/drpy_js/极客资源[资].js" + }, + { + "key":"hipy_js_ok资源[资]", + "name":"ok资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":229, + "ext":"https://hipy.trylyingto.me/files/drpy_js/ok资源[资].js" + }, + { + "key":"hipy_js_索尼资源[资]", + "name":"索尼资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":230, + "ext":"https://hipy.trylyingto.me/files/drpy_js/索尼资源[资].js" + }, + { + "key":"hipy_js_卧龙资源[资]", + "name":"卧龙资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":231, + "ext":"https://hipy.trylyingto.me/files/drpy_js/卧龙资源[资].js" + }, + { + "key":"hipy_js_非凡资源[资]", + "name":"非凡资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":232, + "ext":"https://hipy.trylyingto.me/files/drpy_js/非凡资源[资].js" + }, + { + "key":"hipy_js_素白白[优]", + "name":"素白白[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":233, + "ext":"https://hipy.trylyingto.me/files/drpy_js/素白白[优].js" + }, + { + "key":"hipy_js_腾云驾雾[官]", + "name":"腾云驾雾[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":234, + "ext":"https://hipy.trylyingto.me/files/drpy_js/腾云驾雾[官].js" + }, + { + "key":"hipy_js_360影视[官]", + "name":"360影视[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":235, + "ext":"https://hipy.trylyingto.me/files/drpy_js/360影视[官].js" + }, + { + "key":"hipy_js_奇珍异兽[官]", + "name":"奇珍异兽[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":236, + "ext":"https://hipy.trylyingto.me/files/drpy_js/奇珍异兽[官].js" + }, + { + "key":"hipy_js_百忙无果[官]", + "name":"百忙无果[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":237, + "ext":"https://hipy.trylyingto.me/files/drpy_js/百忙无果[官].js" + }, + { + "key":"hipy_js_阿里土豆[盘]", + "name":"阿里土豆[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":238, + "ext":"https://hipy.trylyingto.me/files/drpy_js/阿里土豆[盘].js" + }, + { + "key":"hipy_js_网飞猫[优]", + "name":"网飞猫[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":239, + "ext":"https://hipy.trylyingto.me/files/drpy_js/网飞猫[优].js" + }, + { + "key":"hipy_js_哔哩教育[官]", + "name":"哔哩教育[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":240, + "ext":"https://hipy.trylyingto.me/files/drpy_js/我的哔哩[官].js?render=1&type=url¶ms=../json/哔哩教育.json" + }, + { + "key":"hipy_js_哔哩大全[官]", + "name":"哔哩大全[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":240, + "ext":"https://hipy.trylyingto.me/files/drpy_js/我的哔哩[官].js?render=1&type=url¶ms=../json/哔哩大全.json" + }, + { + "key":"hipy_js_金鹰资源[资]", + "name":"金鹰资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":241, + "ext":"https://hipy.trylyingto.me/files/drpy_js/金鹰资源[资].js" + }, + { + "key":"hipy_js_南瓜影视[优]", + "name":"南瓜影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":242, + "ext":"https://hipy.trylyingto.me/files/drpy_js/南瓜影视[优].js" + }, + { + "key":"hipy_js_量子资源[资]", + "name":"量子资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":243, + "ext":"https://hipy.trylyingto.me/files/drpy_js/量子资源[资].js" + }, + { + "key":"hipy_js_烧火影视[优]", + "name":"烧火影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":244, + "ext":"https://hipy.trylyingto.me/files/drpy_js/烧火影视[优].js" + }, + { + "key":"hipy_js_可可影视[优]", + "name":"可可影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":245, + "ext":"https://hipy.trylyingto.me/files/drpy_js/可可影视[优].js" + }, + { + "key":"hipy_js_魔都资源[资]", + "name":"魔都资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":246, + "ext":"https://hipy.trylyingto.me/files/drpy_js/魔都资源[资].js" + }, + { + "key":"hipy_js_农民影视[优]", + "name":"农民影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":247, + "ext":"https://hipy.trylyingto.me/files/drpy_js/农民影视[优].js" + }, + { + "key":"hipy_js_爱看机器人[虫]", + "name":"爱看机器人[虫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":248, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看机器人[虫].js" + }, + { + "key":"hipy_js_量子影视[资]", + "name":"量子影视[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":249, + "ext":"https://hipy.trylyingto.me/files/drpy_js/量子影视[资].js" + }, + { + "key":"hipy_js_暴风资源[资]", + "name":"暴风资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":250, + "ext":"https://hipy.trylyingto.me/files/drpy_js/暴风资源[资].js" + }, + { + "key":"hipy_js_豆瓣[官]", + "name":"豆瓣[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":251, + "ext":"https://hipy.trylyingto.me/files/drpy_js/豆瓣[官].js?render=1" + }, + { + "key":"hipy_js_玩偶哥哥[盘]", + "name":"玩偶哥哥[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":252, + "ext":"https://hipy.trylyingto.me/files/drpy_js/玩偶哥哥[盘].js" + }, + { + "key":"hipy_js_LIBVIO[优]", + "name":"LIBVIO[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":253, + "ext":"https://hipy.trylyingto.me/files/drpy_js/LIBVIO[优].js" + }, + { + "key":"hipy_js_兔小贝[儿]", + "name":"兔小贝[儿](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":254, + "ext":"https://hipy.trylyingto.me/files/drpy_js/兔小贝[儿].js" + }, + { + "key":"hipy_js_花子动漫[漫]", + "name":"花子动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":255, + "ext":"https://hipy.trylyingto.me/files/drpy_js/花子动漫[漫].js" + }, + { + "key":"hipy_js_优酷[官]", + "name":"优酷[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":256, + "ext":"https://hipy.trylyingto.me/files/drpy_js/优酷[官].js" + }, + { + "key":"hipy_js_哔哩影视[官]", + "name":"哔哩影视[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":257, + "ext":"https://hipy.trylyingto.me/files/drpy_js/哔哩影视[官].js?render=1" + }, + { + "key":"hipy_js_菜狗[官]", + "name":"菜狗[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":258, + "ext":"https://hipy.trylyingto.me/files/drpy_js/菜狗[官].js" + }, + { + "key":"hipy_js_极速资源[资]", + "name":"极速资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":259, + "ext":"https://hipy.trylyingto.me/files/drpy_js/极速资源[资].js" + }, + { + "key":"hipy_js_荐片[优]", + "name":"荐片[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":260, + "ext":"https://hipy.trylyingto.me/files/drpy_js/荐片[优].js" + }, + { + "key":"hipy_js_思古影视[V2]", + "name":"思古影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":261, + "ext":"https://hipy.trylyingto.me/files/drpy_js/思古影视[V2].js" + }, + { + "key":"hipy_js_蛋蛋剧", + "name":"蛋蛋剧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":264, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蛋蛋剧.js" + }, + { + "key":"hipy_js_蛋蛋赞", + "name":"蛋蛋赞(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":265, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蛋蛋赞.js" + }, + { + "key":"hipy_js_时光影院", + "name":"时光影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":269, + "ext":"https://hipy.trylyingto.me/files/drpy_js/时光影院.js" + }, + { + "key":"hipy_js_皮皮影视", + "name":"皮皮影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":271, + "ext":"https://hipy.trylyingto.me/files/drpy_js/皮皮影视.js" + }, + { + "key":"hipy_js_影视看吧", + "name":"影视看吧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":272, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影视看吧.js" + }, + { + "key":"hipy_js_看看影视", + "name":"看看影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":273, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看看影视.js" + }, + { + "key":"hipy_js_酷客影视", + "name":"酷客影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":274, + "ext":"https://hipy.trylyingto.me/files/drpy_js/酷客影视.js" + }, + { + "key":"hipy_js_热播之家", + "name":"热播之家(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":275, + "ext":"https://hipy.trylyingto.me/files/drpy_js/热播之家.js" + }, + { + "key":"hipy_js_蓝光影视", + "name":"蓝光影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":276, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蓝光影视.js" + }, + { + "key":"hipy_js_星云影视", + "name":"星云影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":277, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星云影视.js" + }, + { + "key":"hipy_js_西屋影视", + "name":"西屋影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":278, + "ext":"https://hipy.trylyingto.me/files/drpy_js/西屋影视.js" + }, + { + "key":"hipy_js_孜然影视", + "name":"孜然影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":279, + "ext":"https://hipy.trylyingto.me/files/drpy_js/孜然影视.js" + }, + { + "key":"hipy_js_人人影视", + "name":"人人影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":280, + "ext":"https://hipy.trylyingto.me/files/drpy_js/人人影视.js" + }, + { + "key":"hipy_js_快看影视", + "name":"快看影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":281, + "ext":"https://hipy.trylyingto.me/files/drpy_js/快看影视.js" + }, + { + "key":"hipy_js_星空影院", + "name":"星空影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":282, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星空影院.js" + }, + { + "key":"hipy_js_HDmoli", + "name":"HDmoli(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":283, + "ext":"https://hipy.trylyingto.me/files/drpy_js/HDmoli.js" + }, + { + "key":"hipy_js_4khdr", + "name":"4khdr(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":284, + "ext":"https://hipy.trylyingto.me/files/drpy_js/4khdr.js" + }, + { + "key":"hipy_js_美益达", + "name":"美益达(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":286, + "ext":"https://hipy.trylyingto.me/files/drpy_js/美益达.js" + }, + { + "key":"hipy_js_干饭影视", + "name":"干饭影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":287, + "ext":"https://hipy.trylyingto.me/files/drpy_js/干饭影视.js" + }, + { + "key":"hipy_js_即看影视", + "name":"即看影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":288, + "ext":"https://hipy.trylyingto.me/files/drpy_js/即看影视.js" + }, + { + "key":"hipy_js_夕云影视", + "name":"夕云影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":289, + "ext":"https://hipy.trylyingto.me/files/drpy_js/夕云影视.js" + }, + { + "key":"hipy_js_8号影院", + "name":"8号影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":293, + "ext":"https://hipy.trylyingto.me/files/drpy_js/8号影院.js" + }, + { + "key":"hipy_js_茶语资源[资]", + "name":"茶语资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":294, + "ext":"https://hipy.trylyingto.me/files/drpy_js/茶语资源[资].js" + }, + { + "key":"hipy_js_电影先生", + "name":"电影先生(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":295, + "ext":"https://hipy.trylyingto.me/files/drpy_js/电影先生.js" + }, + { + "key":"hipy_js_鸭奈飞影视", + "name":"鸭奈飞影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":296, + "ext":"https://hipy.trylyingto.me/files/drpy_js/鸭奈飞影视.js" + }, + { + "key":"hipy_js_新视觉", + "name":"新视觉(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":300, + "ext":"https://hipy.trylyingto.me/files/drpy_js/新视觉.js" + }, + { + "key":"hipy_js_4k剧院", + "name":"4k剧院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":301, + "ext":"https://hipy.trylyingto.me/files/drpy_js/4k剧院.js" + }, + { + "key":"hipy_js_魔方影视", + "name":"魔方影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":303, + "ext":"https://hipy.trylyingto.me/files/drpy_js/魔方影视.js" + }, + { + "key":"hipy_js_往往影视[慢]", + "name":"往往影视[慢](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":304, + "ext":"https://hipy.trylyingto.me/files/drpy_js/往往影视[慢].js" + }, + { + "key":"hipy_js_爱看影院", + "name":"爱看影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":305, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看影院.js" + }, + { + "key":"hipy_js_一号影院[搜]", + "name":"一号影院[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":306, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一号影院[搜].js" + }, + { + "key":"hipy_js_首发网", + "name":"首发网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":307, + "ext":"https://hipy.trylyingto.me/files/drpy_js/首发网.js" + }, + { + "key":"hipy_js_星辰影视", + "name":"星辰影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":308, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星辰影视.js" + }, + { + "key":"hipy_js_大中国", + "name":"大中国(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":309, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大中国.js" + }, + { + "key":"hipy_js_剧圈圈", + "name":"剧圈圈(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":310, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧圈圈.js" + }, + { + "key":"hipy_js_短剧TV网", + "name":"短剧TV网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":311, + "ext":"https://hipy.trylyingto.me/files/drpy_js/短剧TV网.js" + }, + { + "key":"hipy_js_黑木耳资源[资]", + "name":"黑木耳资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":312, + "ext":"https://hipy.trylyingto.me/files/drpy_js/黑木耳资源[资].js" + }, + { + "key":"hipy_js_看客影院", + "name":"看客影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":313, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看客影院.js" + }, + { + "key":"hipy_js_iFun", + "name":"iFun(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":314, + "ext":"https://hipy.trylyingto.me/files/drpy_js/iFun.js" + }, + { + "key":"hipy_js_如意影视", + "name":"如意影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":315, + "ext":"https://hipy.trylyingto.me/files/drpy_js/如意影视.js" + }, + { + "key":"hipy_js_电影天堂", + "name":"电影天堂(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":316, + "ext":"https://hipy.trylyingto.me/files/drpy_js/电影天堂.js" + }, + { + "key":"hipy_js_短剧天堂", + "name":"短剧天堂(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":317, + "ext":"https://hipy.trylyingto.me/files/drpy_js/短剧天堂.js" + }, + { + "key":"hipy_js_飘花影院", + "name":"飘花影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":318, + "ext":"https://hipy.trylyingto.me/files/drpy_js/飘花影院.js" + }, + { + "key":"hipy_js_我播", + "name":"我播(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":319, + "ext":"https://hipy.trylyingto.me/files/drpy_js/我播.js" + }, + { + "key":"hipy_js_想看影院", + "name":"想看影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":320, + "ext":"https://hipy.trylyingto.me/files/drpy_js/想看影院.js" + }, + { + "key":"hipy_js_小鱼影视", + "name":"小鱼影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":321, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小鱼影视.js" + }, + { + "key":"hipy_js_爱看农民[优]", + "name":"爱看农民[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":324, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看农民[优].js" + }, + { + "key":"hipy_js_爱看农民2[优]", + "name":"爱看农民2[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":325, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看农民2[优].js" + }, + { + "key":"hipy_js_爱看hd", + "name":"爱看hd(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":326, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看hd.js" + }, + { + "key":"hipy_js_胖虎影视", + "name":"胖虎影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":327, + "ext":"https://hipy.trylyingto.me/files/drpy_js/胖虎影视.js" + }, + { + "key":"hipy_js_小站盘[搜]", + "name":"小站盘[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":329, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小站盘[搜].js" + }, + { + "key":"hipy_js_狗狗盘[搜]", + "name":"狗狗盘[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":330, + "ext":"https://hipy.trylyingto.me/files/drpy_js/狗狗盘[搜].js" + }, + { + "key":"hipy_js_暖光影视", + "name":"暖光影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":333, + "ext":"https://hipy.trylyingto.me/files/drpy_js/暖光影视.js" + }, + { + "key":"hipy_js_被窝电影", + "name":"被窝电影(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":334, + "ext":"https://hipy.trylyingto.me/files/drpy_js/被窝电影.js" + }, + { + "key":"hipy_js_蚂蚁影视", + "name":"蚂蚁影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":335, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蚂蚁影视.js" + }, + { + "key":"hipy_js_影搜[搜]", + "name":"影搜[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":336, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影搜[搜].js" + }, + { + "key":"hipy_js_易搜[搜]", + "name":"易搜[搜](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":338, + "ext":"https://hipy.trylyingto.me/files/drpy_js/易搜[搜].js" + }, + { + "key":"hipy_js_短剧在线", + "name":"短剧在线(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":345, + "ext":"https://hipy.trylyingto.me/files/drpy_js/短剧在线.js" + }, + { + "key":"hipy_js_哈皮影视[优]", + "name":"哈皮影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":346, + "ext":"https://hipy.trylyingto.me/files/drpy_js/哈皮影视[优].js" + }, + { + "key":"hipy_js_笔趣阁[书]", + "name":"笔趣阁[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":347, + "ext":"https://hipy.trylyingto.me/files/drpy_js/笔趣阁[书].js" + }, + { + "key":"hipy_js_番茄小说[书]", + "name":"番茄小说[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":348, + "ext":"https://hipy.trylyingto.me/files/drpy_js/番茄小说[书].js" + }, + { + "key":"hipy_js_7猫小说[书]", + "name":"7猫小说[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":350, + "ext":"https://hipy.trylyingto.me/files/drpy_js/7猫小说[书].js" + }, + { + "key":"hipy_js_蜡笔[盘]", + "name":"蜡笔[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":353, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蜡笔[盘].js" + }, + { + "key":"hipy_js_虾酱追剧", + "name":"虾酱追剧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":354, + "ext":"https://hipy.trylyingto.me/files/drpy_js/虾酱追剧.js" + }, + { + "key":"hipy_js_黑料不打烊-z", + "name":"黑料不打烊-z(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":356, + "ext":"https://hipy.trylyingto.me/files/drpy_js/黑料不打烊-z.js" + }, + { + "key":"hipy_js_六月听书[听]", + "name":"六月听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":358, + "ext":"https://hipy.trylyingto.me/files/drpy_js/六月听书[听].js" + }, + { + "key":"hipy_js_i275听书[听]", + "name":"i275听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":360, + "ext":"https://hipy.trylyingto.me/files/drpy_js/i275听书[听].js" + }, + { + "key":"hipy_js_播客[听]", + "name":"播客[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":361, + "ext":"https://hipy.trylyingto.me/files/drpy_js/播客[听].js" + }, + { + "key":"hipy_js_爱上你听书网[听]", + "name":"爱上你听书网[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":362, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱上你听书网[听].js" + }, + { + "key":"hipy_js_海洋听书[听]", + "name":"海洋听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":363, + "ext":"https://hipy.trylyingto.me/files/drpy_js/海洋听书[听].js" + }, + { + "key":"hipy_js_有声绘本网[听]", + "name":"有声绘本网[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":364, + "ext":"https://hipy.trylyingto.me/files/drpy_js/有声绘本网[听].js" + }, + { + "key":"hipy_js_博看听书[听]", + "name":"博看听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":365, + "ext":"https://hipy.trylyingto.me/files/drpy_js/博看听书[听].js" + }, + { + "key":"hipy_js_有声小说吧[听]", + "name":"有声小说吧[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":367, + "ext":"https://hipy.trylyingto.me/files/drpy_js/有声小说吧[听].js" + }, + { + "key":"hipy_js_顶点小说[书]", + "name":"顶点小说[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":369, + "ext":"https://hipy.trylyingto.me/files/drpy_js/顶点小说[书].js" + }, + { + "key":"hipy_js_丫丫电子书[书]", + "name":"丫丫电子书[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":372, + "ext":"https://hipy.trylyingto.me/files/drpy_js/丫丫电子书[书].js" + }, + { + "key":"hipy_js_飞翔鸟[书]", + "name":"飞翔鸟[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":375, + "ext":"https://hipy.trylyingto.me/files/drpy_js/飞翔鸟[书].js" + }, + { + "key":"hipy_js_顶点小说2[书]", + "name":"顶点小说2[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":376, + "ext":"https://hipy.trylyingto.me/files/drpy_js/顶点小说2[书].js" + }, + { + "key":"hipy_js_橘子柚[盘]", + "name":"橘子柚[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":379, + "ext":"https://hipy.trylyingto.me/files/drpy_js/橘子柚[盘].js" + }, + { + "key":"hipy_js_热片网", + "name":"热片网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":382, + "ext":"https://hipy.trylyingto.me/files/drpy_js/热片网.js" + }, + { + "key":"hipy_js_子子影视", + "name":"子子影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":383, + "ext":"https://hipy.trylyingto.me/files/drpy_js/子子影视.js" + }, + { + "key":"hipy_js_天启", + "name":"天启(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":384, + "ext":"https://hipy.trylyingto.me/files/drpy_js/天启.js" + }, + { + "key":"hipy_js_我看书斋[书]", + "name":"我看书斋[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":385, + "ext":"https://hipy.trylyingto.me/files/drpy_js/我看书斋[书].js" + }, + { + "key":"hipy_js_一点视频[密]", + "name":"一点视频[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":386, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一点视频[密].js" + }, + { + "key":"hipy_js_九妖仓库[密]", + "name":"九妖仓库[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":388, + "ext":"https://hipy.trylyingto.me/files/drpy_js/九妖仓库[密].js" + }, + { + "key":"hipy_js_千百视频[密]", + "name":"千百视频[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":389, + "ext":"https://hipy.trylyingto.me/files/drpy_js/千百视频[密].js" + }, + { + "key":"hipy_js_老司视频[密]", + "name":"老司视频[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":390, + "ext":"https://hipy.trylyingto.me/files/drpy_js/老司视频[密].js" + }, + { + "key":"hipy_js_剧圈圈[自动]", + "name":"剧圈圈[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":391, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧圈圈[自动].js" + }, + { + "key":"hipy_js_黑料不打烊[密]", + "name":"黑料不打烊[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":392, + "ext":"https://hipy.trylyingto.me/files/drpy_js/黑料不打烊[密].js" + }, + { + "key":"hipy_js_爱爱影院[密]", + "name":"爱爱影院[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":393, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱爱影院[密].js" + }, + { + "key":"hipy_js_29片库[密]", + "name":"29片库[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":394, + "ext":"https://hipy.trylyingto.me/files/drpy_js/29片库[密].js" + }, + { + "key":"hipy_js_草莓秒播[密]", + "name":"草莓秒播[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":395, + "ext":"https://hipy.trylyingto.me/files/drpy_js/草莓秒播[密].js" + }, + { + "key":"hipy_js_绿色仓库[密]", + "name":"绿色仓库[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":396, + "ext":"https://hipy.trylyingto.me/files/drpy_js/绿色仓库[密].js" + }, + { + "key":"hipy_js_Pornhub[密]", + "name":"Pornhub[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":397, + "ext":"https://hipy.trylyingto.me/files/drpy_js/Pornhub[密].js" + }, + { + "key":"hipy_js_乐草TV[密]", + "name":"乐草TV[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":398, + "ext":"https://hipy.trylyingto.me/files/drpy_js/乐草TV[密].js" + }, + { + "key":"hipy_js_酷云影视", + "name":"酷云影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":399, + "ext":"https://hipy.trylyingto.me/files/drpy_js/酷云影视.js" + }, + { + "key":"hipy_js_星辰CT", + "name":"星辰CT(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":400, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星辰CT.js" + }, + { + "key":"hipy_js_地瓜视频[密]", + "name":"地瓜视频[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":402, + "ext":"https://hipy.trylyingto.me/files/drpy_js/地瓜视频[密].js" + }, + { + "key":"hipy_js_神仙影视", + "name":"神仙影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":403, + "ext":"https://hipy.trylyingto.me/files/drpy_js/神仙影视.js" + }, + { + "key":"hipy_js_bilfun(自动)", + "name":"bilfun(自动)(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":404, + "ext":"https://hipy.trylyingto.me/files/drpy_js/bilfun(自动).js" + }, + { + "key":"hipy_js_大师兄影视[优]", + "name":"大师兄影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":407, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大师兄影视[优].js" + }, + { + "key":"hipy_js_看戏网", + "name":"看戏网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":408, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看戏网.js" + }, + { + "key":"hipy_js_黑狐影院", + "name":"黑狐影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":409, + "ext":"https://hipy.trylyingto.me/files/drpy_js/黑狐影院.js" + }, + { + "key":"hipy_js_剧集TV", + "name":"剧集TV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":410, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧集TV.js" + }, + { + "key":"hipy_js_一起看[优]", + "name":"一起看[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":411, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一起看[优].js" + }, + { + "key":"hipy_js_速播小屋", + "name":"速播小屋(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":413, + "ext":"https://hipy.trylyingto.me/files/drpy_js/速播小屋.js" + }, + { + "key":"hipy_js_iku喵[资]", + "name":"iku喵[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":414, + "ext":"https://hipy.trylyingto.me/files/drpy_js/iku喵[资].js" + }, + { + "key":"hipy_js_天天影视", + "name":"天天影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":415, + "ext":"https://hipy.trylyingto.me/files/drpy_js/天天影视.js" + }, + { + "key":"hipy_js_九牛电影", + "name":"九牛电影(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":416, + "ext":"https://hipy.trylyingto.me/files/drpy_js/九牛电影.js" + }, + { + "key":"hipy_js_最新4K", + "name":"最新4K(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":418, + "ext":"https://hipy.trylyingto.me/files/drpy_js/最新4K.js" + }, + { + "key":"hipy_js_JRKAN直播", + "name":"JRKAN直播(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":419, + "ext":"https://hipy.trylyingto.me/files/drpy_js/JRKAN直播.js" + }, + { + "key":"hipy_js_追剧兔", + "name":"追剧兔(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":420, + "ext":"https://hipy.trylyingto.me/files/drpy_js/追剧兔.js" + }, + { + "key":"hipy_js_无插件直播", + "name":"无插件直播(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":421, + "ext":"https://hipy.trylyingto.me/files/drpy_js/无插件直播.js" + }, + { + "key":"hipy_js_猫视界", + "name":"猫视界(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":422, + "ext":"https://hipy.trylyingto.me/files/drpy_js/猫视界.js" + }, + { + "key":"hipy_js_好趣网[播]", + "name":"好趣网[播](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":423, + "ext":"https://hipy.trylyingto.me/files/drpy_js/好趣网[播].js" + }, + { + "key":"hipy_js_漫小肆[画]", + "name":"漫小肆[画](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":424, + "ext":"https://hipy.trylyingto.me/files/drpy_js/漫小肆[画].js" + }, + { + "key":"hipy_js_清风DJ[听]", + "name":"清风DJ[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":425, + "ext":"https://hipy.trylyingto.me/files/drpy_js/清风DJ[听].js" + }, + { + "key":"hipy_js_麻雀视频[优]", + "name":"麻雀视频[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":426, + "ext":"https://hipy.trylyingto.me/files/drpy_js/麻雀视频[优].js" + }, + { + "key":"hipy_js_第一韩漫[画]", + "name":"第一韩漫[画](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":427, + "ext":"https://hipy.trylyingto.me/files/drpy_js/第一韩漫[画].js" + }, + { + "key":"hipy_js_次元城动漫[漫]", + "name":"次元城动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":428, + "ext":"https://hipy.trylyingto.me/files/drpy_js/次元城动漫[漫].js" + }, + { + "key":"hipy_js_OmoFun[漫]", + "name":"OmoFun[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":429, + "ext":"https://hipy.trylyingto.me/files/drpy_js/OmoFun[漫].js" + }, + { + "key":"hipy_js_R18撸[密]", + "name":"R18撸[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":430, + "ext":"https://hipy.trylyingto.me/files/drpy_js/R18撸[密].js" + }, + { + "key":"hipy_js_动漫巴士[漫]", + "name":"动漫巴士[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":431, + "ext":"https://hipy.trylyingto.me/files/drpy_js/动漫巴士[漫].js" + }, + { + "key":"hipy_js_大米动漫[漫]", + "name":"大米动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":432, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大米动漫[漫].js" + }, + { + "key":"hipy_js_樱花动漫[漫]", + "name":"樱花动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":433, + "ext":"https://hipy.trylyingto.me/files/drpy_js/樱花动漫[漫].js" + }, + { + "key":"hipy_js_维奇动漫[漫]", + "name":"维奇动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":434, + "ext":"https://hipy.trylyingto.me/files/drpy_js/维奇动漫[漫].js" + }, + { + "key":"hipy_js_88看球[球]", + "name":"88看球[球](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":435, + "ext":"https://hipy.trylyingto.me/files/drpy_js/88看球[球].js" + }, + { + "key":"hipy_js_云盘资源网[盘]", + "name":"云盘资源网[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":436, + "ext":"https://hipy.trylyingto.me/files/drpy_js/云盘资源网[盘].js" + }, + { + "key":"hipy_js_哔哩直播[官]", + "name":"哔哩直播[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":437, + "ext":"https://hipy.trylyingto.me/files/drpy_js/哔哩直播[官].js" + }, + { + "key":"hipy_js_虎牙直播[官]", + "name":"虎牙直播[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":438, + "ext":"https://hipy.trylyingto.me/files/drpy_js/虎牙直播[官].js" + }, + { + "key":"hipy_js_动漫网[漫]", + "name":"动漫网[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":439, + "ext":"https://hipy.trylyingto.me/files/drpy_js/动漫网[漫].js" + }, + { + "key":"hipy_js_斗鱼直播[官]", + "name":"斗鱼直播[官](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":440, + "ext":"https://hipy.trylyingto.me/files/drpy_js/斗鱼直播[官].js" + }, + { + "key":"hipy_js_恒大影视[密]", + "name":"恒大影视[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":441, + "ext":"https://hipy.trylyingto.me/files/drpy_js/恒大影视[密].js" + }, + { + "key":"hipy_js_爱车MV[听]", + "name":"爱车MV[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":442, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱车MV[听].js" + }, + { + "key":"hipy_js_驴番[漫]", + "name":"驴番[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":443, + "ext":"https://hipy.trylyingto.me/files/drpy_js/驴番[漫].js" + }, + { + "key":"hipy_js_58动漫[漫]", + "name":"58动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":444, + "ext":"https://hipy.trylyingto.me/files/drpy_js/58动漫[漫].js" + }, + { + "key":"hipy_js_多多追剧[优]", + "name":"多多追剧[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":445, + "ext":"https://hipy.trylyingto.me/files/drpy_js/多多追剧[优].js" + }, + { + "key":"hipy_js_相声随身听[听]", + "name":"相声随身听[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":446, + "ext":"https://hipy.trylyingto.me/files/drpy_js/相声随身听[听].js" + }, + { + "key":"hipy_js_广播迷FM[听]", + "name":"广播迷FM[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":447, + "ext":"https://hipy.trylyingto.me/files/drpy_js/广播迷FM[听].js" + }, + { + "key":"hipy_js_包子漫画[画]", + "name":"包子漫画[画](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":448, + "ext":"https://hipy.trylyingto.me/files/drpy_js/包子漫画[画].js" + }, + { + "key":"hipy_js_古风漫画[画]", + "name":"古风漫画[画](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":449, + "ext":"https://hipy.trylyingto.me/files/drpy_js/古风漫画[画].js" + }, + { + "key":"hipy_js_七色番[漫]", + "name":"七色番[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":450, + "ext":"https://hipy.trylyingto.me/files/drpy_js/七色番[漫].js" + }, + { + "key":"hipy_js_MuteFun[漫]", + "name":"MuteFun[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":451, + "ext":"https://hipy.trylyingto.me/files/drpy_js/MuteFun[漫].js" + }, + { + "key":"hipy_js_有声听书网[听]", + "name":"有声听书网[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":452, + "ext":"https://hipy.trylyingto.me/files/drpy_js/有声听书网[听].js" + }, + { + "key":"hipy_js_NT动漫[漫]", + "name":"NT动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":453, + "ext":"https://hipy.trylyingto.me/files/drpy_js/NT动漫[漫].js" + }, + { + "key":"hipy_js_星辰[优]", + "name":"星辰[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":454, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星辰[优].js" + }, + { + "key":"hipy_js_飞刀资源[资]", + "name":"飞刀资源[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":455, + "ext":"https://hipy.trylyingto.me/files/drpy_js/飞刀资源[资].js" + }, + { + "key":"hipy_js_七新电影网", + "name":"七新电影网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":457, + "ext":"https://hipy.trylyingto.me/files/drpy_js/七新电影网.js" + }, + { + "key":"hipy_js_路视频[密]", + "name":"路视频[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":458, + "ext":"https://hipy.trylyingto.me/files/drpy_js/路视频[密].js" + }, + { + "key":"hipy_js_刺猬影视", + "name":"刺猬影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":459, + "ext":"https://hipy.trylyingto.me/files/drpy_js/刺猬影视.js" + }, + { + "key":"hipy_js_文才[资]", + "name":"文才[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":460, + "ext":"https://hipy.trylyingto.me/files/drpy_js/文才[资].js" + }, + { + "key":"hipy_js_采王道长[合]", + "name":"采王道长[合](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":463, + "ext":"https://hipy.trylyingto.me/files/drpy_js/采集之王[合].js?type=url¶ms=../json/采集静态.json$1" + }, + { + "key":"hipy_js_采王zy[密]", + "name":"采王zy[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":463, + "ext":"https://hipy.trylyingto.me/files/drpy_js/采集之王[合].js?type=url¶ms=../json/采集[zy]静态.json$1" + }, + { + "key":"hipy_js_采王成人[密]", + "name":"采王成人[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":463, + "ext":"https://hipy.trylyingto.me/files/drpy_js/采集之王[合].js?type=url¶ms=../json/采集[密]静态.json$1$" + }, + { + "key":"hipy_js_剧哥哥", + "name":"剧哥哥(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":467, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧哥哥.js" + }, + { + "key":"hipy_js_策驰影院(自动)", + "name":"策驰影院(自动)(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":468, + "ext":"https://hipy.trylyingto.me/files/drpy_js/策驰影院(自动).js" + }, + { + "key":"hipy_js_豆角网", + "name":"豆角网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":469, + "ext":"https://hipy.trylyingto.me/files/drpy_js/豆角网.js" + }, + { + "key":"hipy_js_西瓜影院", + "name":"西瓜影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":470, + "ext":"https://hipy.trylyingto.me/files/drpy_js/西瓜影院.js" + }, + { + "key":"hipy_js_script直播[飞]", + "name":"script直播[飞](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":473, + "ext":"https://hipy.trylyingto.me/files/drpy_js/script直播[飞].js" + }, + { + "key":"hipy_js_分享短视频", + "name":"分享短视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":474, + "ext":"https://hipy.trylyingto.me/files/drpy_js/分享短视频.js" + }, + { + "key":"hipy_js_贝乐虎[儿]", + "name":"贝乐虎[儿](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":475, + "ext":"https://hipy.trylyingto.me/files/drpy_js/贝乐虎[儿].js" + }, + { + "key":"hipy_js_KTV歌厅[听]", + "name":"KTV歌厅[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":476, + "ext":"https://hipy.trylyingto.me/files/drpy_js/KTV歌厅[听].js" + }, + { + "key":"hipy_js_网飞.TV", + "name":"网飞.TV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":477, + "ext":"https://hipy.trylyingto.me/files/drpy_js/网飞.TV.js" + }, + { + "key":"hipy_js_啊哈DJ[听]", + "name":"啊哈DJ[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":478, + "ext":"https://hipy.trylyingto.me/files/drpy_js/啊哈DJ[听].js" + }, + { + "key":"hipy_js_ASMR[听]", + "name":"ASMR[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":479, + "ext":"https://hipy.trylyingto.me/files/drpy_js/ASMR[听].js" + }, + { + "key":"hipy_js_直播转点播[合]", + "name":"直播转点播[合](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":480, + "ext":"https://hipy.trylyingto.me/files/drpy_js/直播转点播[合].js?type=url¶ms=../json/live2cms.json" + }, + { + "key":"hipy_js_飞狗影院[密]", + "name":"飞狗影院[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":485, + "ext":"https://hipy.trylyingto.me/files/drpy_js/飞狗影院[密].js" + }, + { + "key":"hipy_js_奶狗影视[慢]", + "name":"奶狗影视[慢](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":486, + "ext":"https://hipy.trylyingto.me/files/drpy_js/奶狗影视[慢].js" + }, + { + "key":"hipy_js_饭团影视", + "name":"饭团影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":487, + "ext":"https://hipy.trylyingto.me/files/drpy_js/饭团影视.js" + }, + { + "key":"hipy_js_影剧星球", + "name":"影剧星球(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":488, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影剧星球.js" + }, + { + "key":"hipy_js_影视大全", + "name":"影视大全(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":489, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影视大全.js" + }, + { + "key":"hipy_js_影视控", + "name":"影视控(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":490, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影视控.js" + }, + { + "key":"hipy_js_悠悠影视", + "name":"悠悠影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":491, + "ext":"https://hipy.trylyingto.me/files/drpy_js/悠悠影视.js" + }, + { + "key":"hipy_js_爱你短剧", + "name":"爱你短剧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":492, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱你短剧.js" + }, + { + "key":"hipy_js_三集电影[自动]", + "name":"三集电影[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":493, + "ext":"https://hipy.trylyingto.me/files/drpy_js/三集电影[自动].js" + }, + { + "key":"hipy_js_来看点播[自动]", + "name":"来看点播[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":494, + "ext":"https://hipy.trylyingto.me/files/drpy_js/来看点播[自动].js" + }, + { + "key":"hipy_js_冠建影视", + "name":"冠建影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":495, + "ext":"https://hipy.trylyingto.me/files/drpy_js/冠建影视.js" + }, + { + "key":"hipy_js_影渣渣影视", + "name":"影渣渣影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":496, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影渣渣影视.js" + }, + { + "key":"hipy_js_旺旺影视", + "name":"旺旺影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":497, + "ext":"https://hipy.trylyingto.me/files/drpy_js/旺旺影视.js" + }, + { + "key":"hipy_js_神马影院[自动]", + "name":"神马影院[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":498, + "ext":"https://hipy.trylyingto.me/files/drpy_js/神马影院[自动].js" + }, + { + "key":"hipy_js_网飞啦[自动]", + "name":"网飞啦[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":499, + "ext":"https://hipy.trylyingto.me/files/drpy_js/网飞啦[自动].js" + }, + { + "key":"hipy_js_嘀哩嘀哩", + "name":"嘀哩嘀哩(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":500, + "ext":"https://hipy.trylyingto.me/files/drpy_js/嘀哩嘀哩.js" + }, + { + "key":"hipy_js_刷剧网", + "name":"刷剧网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":501, + "ext":"https://hipy.trylyingto.me/files/drpy_js/刷剧网.js" + }, + { + "key":"hipy_js_auete", + "name":"auete(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":502, + "ext":"https://hipy.trylyingto.me/files/drpy_js/auete.js" + }, + { + "key":"hipy_js_剧迷", + "name":"剧迷(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":503, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧迷.js" + }, + { + "key":"hipy_js_映播TV", + "name":"映播TV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":504, + "ext":"https://hipy.trylyingto.me/files/drpy_js/映播TV.js" + }, + { + "key":"hipy_js_毒蛇电影[优]", + "name":"毒蛇电影[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":505, + "ext":"https://hipy.trylyingto.me/files/drpy_js/毒蛇电影[优].js" + }, + { + "key":"hipy_js_小女18[密]", + "name":"小女18[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":506, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小女18[密].js" + }, + { + "key":"hipy_js_易点看影院[自动]", + "name":"易点看影院[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":507, + "ext":"https://hipy.trylyingto.me/files/drpy_js/易点看影院[自动].js" + }, + { + "key":"hipy_js_你好帅影院", + "name":"你好帅影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":508, + "ext":"https://hipy.trylyingto.me/files/drpy_js/你好帅影院.js" + }, + { + "key":"hipy_js_大千视界", + "name":"大千视界(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":511, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大千视界.js" + }, + { + "key":"hipy_js_游子视频", + "name":"游子视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":512, + "ext":"https://hipy.trylyingto.me/files/drpy_js/游子视频.js" + }, + { + "key":"hipy_js_看客视频", + "name":"看客视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":514, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看客视频.js" + }, + { + "key":"hipy_js_奈飞中文[自动]", + "name":"奈飞中文[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":515, + "ext":"https://hipy.trylyingto.me/files/drpy_js/奈飞中文[自动].js" + }, + { + "key":"hipy_js_饺子影院", + "name":"饺子影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":516, + "ext":"https://hipy.trylyingto.me/files/drpy_js/饺子影院.js" + }, + { + "key":"hipy_js_无忧影视", + "name":"无忧影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":517, + "ext":"https://hipy.trylyingto.me/files/drpy_js/无忧影视.js" + }, + { + "key":"hipy_js_咖啡影视", + "name":"咖啡影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":518, + "ext":"https://hipy.trylyingto.me/files/drpy_js/咖啡影视.js" + }, + { + "key":"hipy_js_笔趣阁13[书]", + "name":"笔趣阁13[书](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":519, + "ext":"https://hipy.trylyingto.me/files/drpy_js/笔趣阁13[书].js" + }, + { + "key":"hipy_js_360吧[球]", + "name":"360吧[球](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":520, + "ext":"https://hipy.trylyingto.me/files/drpy_js/360吧[球].js" + }, + { + "key":"hipy_js_看了么", + "name":"看了么(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":521, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看了么.js" + }, + { + "key":"hipy_js_童趣[儿]", + "name":"童趣[儿](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":522, + "ext":"https://hipy.trylyingto.me/files/drpy_js/童趣[儿].js" + }, + { + "key":"hipy_js_36直播[密]", + "name":"36直播[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":523, + "ext":"https://hipy.trylyingto.me/files/drpy_js/36直播[密].js" + }, + { + "key":"hipy_js_路漫漫[漫]", + "name":"路漫漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":524, + "ext":"https://hipy.trylyingto.me/files/drpy_js/路漫漫[漫].js" + }, + { + "key":"hipy_js_老白故事[听]", + "name":"老白故事[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":525, + "ext":"https://hipy.trylyingto.me/files/drpy_js/老白故事[听].js" + }, + { + "key":"hipy_js_世纪DJ音乐网[听]", + "name":"世纪DJ音乐网[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":527, + "ext":"https://hipy.trylyingto.me/files/drpy_js/世纪DJ音乐网[听].js" + }, + { + "key":"hipy_js_闪雷电", + "name":"闪雷电(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":528, + "ext":"https://hipy.trylyingto.me/files/drpy_js/闪雷电.js" + }, + { + "key":"hipy_js_飞鱼影视", + "name":"飞鱼影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":529, + "ext":"https://hipy.trylyingto.me/files/drpy_js/飞鱼影视.js" + }, + { + "key":"hipy_js_天龙影院", + "name":"天龙影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":530, + "ext":"https://hipy.trylyingto.me/files/drpy_js/天龙影院.js" + }, + { + "key":"hipy_js_桃子影视[优]", + "name":"桃子影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":534, + "ext":"https://hipy.trylyingto.me/files/drpy_js/桃子影视[优].js" + }, + { + "key":"hipy_js_爱看短剧[盘]", + "name":"爱看短剧[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":535, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱看短剧[盘].js" + }, + { + "key":"hipy_js_爱优影视[自动]", + "name":"爱优影视[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":536, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱优影视[自动].js" + }, + { + "key":"hipy_js_兄弟影视[优]", + "name":"兄弟影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":537, + "ext":"https://hipy.trylyingto.me/files/drpy_js/兄弟影视[优].js" + }, + { + "key":"hipy_js_影视工厂", + "name":"影视工厂(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":538, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影视工厂.js" + }, + { + "key":"hipy_js_至臻[盘]", + "name":"至臻[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":539, + "ext":"https://hipy.trylyingto.me/files/drpy_js/至臻[盘].js" + }, + { + "key":"hipy_js_B站影视", + "name":"B站影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":540, + "ext":"https://hipy.trylyingto.me/files/drpy_js/B站影视.js" + }, + { + "key":"hipy_js_两个BT", + "name":"两个BT(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":541, + "ext":"https://hipy.trylyingto.me/files/drpy_js/两个BT.js" + }, + { + "key":"hipy_js_宇航影视", + "name":"宇航影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":542, + "ext":"https://hipy.trylyingto.me/files/drpy_js/宇航影视.js" + }, + { + "key":"hipy_js_小白菜电影", + "name":"小白菜电影(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":543, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小白菜电影.js" + }, + { + "key":"hipy_js_影视工场", + "name":"影视工场(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":544, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影视工场.js" + }, + { + "key":"hipy_js_低端", + "name":"低端(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":545, + "ext":"https://hipy.trylyingto.me/files/drpy_js/低端.js" + }, + { + "key":"hipy_js_爱迪影视", + "name":"爱迪影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":547, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱迪影视.js" + }, + { + "key":"hipy_js_我爱跟剧", + "name":"我爱跟剧(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":548, + "ext":"https://hipy.trylyingto.me/files/drpy_js/我爱跟剧.js" + }, + { + "key":"hipy_js_北川影视", + "name":"北川影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":549, + "ext":"https://hipy.trylyingto.me/files/drpy_js/北川影视.js" + }, + { + "key":"hipy_js_朴樱影视", + "name":"朴樱影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":550, + "ext":"https://hipy.trylyingto.me/files/drpy_js/朴樱影视.js" + }, + { + "key":"hipy_js_哔嘀影视[优]", + "name":"哔嘀影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":551, + "ext":"https://hipy.trylyingto.me/files/drpy_js/哔嘀影视[优].js" + }, + { + "key":"hipy_js_539影视", + "name":"539影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":552, + "ext":"https://hipy.trylyingto.me/files/drpy_js/539影视.js" + }, + { + "key":"hipy_js_348电影网", + "name":"348电影网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":553, + "ext":"https://hipy.trylyingto.me/files/drpy_js/348电影网.js" + }, + { + "key":"hipy_js_达达龟", + "name":"达达龟(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":554, + "ext":"https://hipy.trylyingto.me/files/drpy_js/达达龟.js" + }, + { + "key":"hipy_js_米爱影视", + "name":"米爱影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":555, + "ext":"https://hipy.trylyingto.me/files/drpy_js/米爱影视.js" + }, + { + "key":"hipy_js_FreeOKLOL", + "name":"FreeOKLOL(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":557, + "ext":"https://hipy.trylyingto.me/files/drpy_js/FreeOKLOL.js" + }, + { + "key":"hipy_js_火狐影视", + "name":"火狐影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":559, + "ext":"https://hipy.trylyingto.me/files/drpy_js/火狐影视.js" + }, + { + "key":"hipy_js_4K-AV", + "name":"4K-AV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":560, + "ext":"https://hipy.trylyingto.me/files/drpy_js/4K-AV.js" + }, + { + "key":"hipy_js_星芽短剧[优]", + "name":"星芽短剧[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":561, + "ext":"https://hipy.trylyingto.me/files/drpy_js/星芽短剧[优].js" + }, + { + "key":"hipy_js_PTT追剧大师", + "name":"PTT追剧大师(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":562, + "ext":"https://hipy.trylyingto.me/files/drpy_js/PTT追剧大师.js" + }, + { + "key":"hipy_js_PTT[优]", + "name":"PTT[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":563, + "ext":"https://hipy.trylyingto.me/files/drpy_js/PTT[优].js" + }, + { + "key":"hipy_js_酷我听书[听]", + "name":"酷我听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":564, + "ext":"https://hipy.trylyingto.me/files/drpy_js/酷我听书[听].js" + }, + { + "key":"hipy_js_喜马拉雅[听]", + "name":"喜马拉雅[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":565, + "ext":"https://hipy.trylyingto.me/files/drpy_js/喜马拉雅[听].js" + }, + { + "key":"hipy_py_emby", + "name":"emby(hipy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/hipy/emby.py", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":567, + "ext":"" + }, + { + "key":"hipy_js_一曲肝肠断", + "name":"一曲肝肠断(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":569, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一曲肝肠断.js" + }, + { + "key":"hipy_js_cally66", + "name":"cally66(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":570, + "ext":"https://hipy.trylyingto.me/files/drpy_js/cally66.js" + }, + { + "key":"hipy_js_一支穿云箭", + "name":"一支穿云箭(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":571, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一支穿云箭.js" + }, + { + "key":"hipy_js_中华听书网[听]", + "name":"中华听书网[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":572, + "ext":"https://hipy.trylyingto.me/files/drpy_js/中华听书网[听].js" + }, + { + "key":"hipy_js_番号资源[密]", + "name":"番号资源[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":573, + "ext":"https://hipy.trylyingto.me/files/drpy_js/番号资源[密].js" + }, + { + "key":"hipy_js_310直播[球]", + "name":"310直播[球](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":574, + "ext":"https://hipy.trylyingto.me/files/drpy_js/310直播[球].js" + }, + { + "key":"hipy_js_色库[密]", + "name":"色库[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":575, + "ext":"https://hipy.trylyingto.me/files/drpy_js/色库[密].js" + }, + { + "key":"hipy_js_01看球[球]", + "name":"01看球[球](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":578, + "ext":"https://hipy.trylyingto.me/files/drpy_js/01看球[球].js" + }, + { + "key":"hipy_js_速讯影院", + "name":"速讯影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":579, + "ext":"https://hipy.trylyingto.me/files/drpy_js/速讯影院.js" + }, + { + "key":"hipy_js_咕咕番[漫]", + "name":"咕咕番[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":580, + "ext":"https://hipy.trylyingto.me/files/drpy_js/咕咕番[漫].js" + }, + { + "key":"hipy_js_臭蛋蛋", + "name":"臭蛋蛋(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":581, + "ext":"https://hipy.trylyingto.me/files/drpy_js/臭蛋蛋.js" + }, + { + "key":"hipy_js_金牌影院", + "name":"金牌影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":582, + "ext":"https://hipy.trylyingto.me/files/drpy_js/金牌影院.js" + }, + { + "key":"hipy_js_种子音乐[听]", + "name":"种子音乐[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":583, + "ext":"https://hipy.trylyingto.me/files/drpy_js/种子音乐[听].js" + }, + { + "key":"hipy_js_萌番[漫]", + "name":"萌番[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":584, + "ext":"https://hipy.trylyingto.me/files/drpy_js/萌番[漫].js" + }, + { + "key":"hipy_js_那兔视频", + "name":"那兔视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":585, + "ext":"https://hipy.trylyingto.me/files/drpy_js/那兔视频.js" + }, + { + "key":"hipy_js_旋风视频", + "name":"旋风视频(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":586, + "ext":"https://hipy.trylyingto.me/files/drpy_js/旋风视频.js" + }, + { + "key":"hipy_js_文才2[资]", + "name":"文才2[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":0, + "order_num":587, + "ext":"https://hipy.trylyingto.me/files/drpy_js/文才2[资].js" + }, + { + "key":"hipy_js_电影狗", + "name":"电影狗(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":589, + "ext":"https://hipy.trylyingto.me/files/drpy_js/电影狗.js" + }, + { + "key":"hipy_js_家庭影视", + "name":"家庭影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":590, + "ext":"https://hipy.trylyingto.me/files/drpy_js/家庭影视.js" + }, + { + "key":"hipy_js_小宝影院", + "name":"小宝影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":591, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小宝影院.js" + }, + { + "key":"hipy_js_看呀看", + "name":"看呀看(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":592, + "ext":"https://hipy.trylyingto.me/files/drpy_js/看呀看.js" + }, + { + "key":"hipy_js_爱弹幕[漫]", + "name":"爱弹幕[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":593, + "ext":"https://hipy.trylyingto.me/files/drpy_js/爱弹幕[漫].js" + }, + { + "key":"hipy_js_怡萱动漫[漫]", + "name":"怡萱动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":594, + "ext":"https://hipy.trylyingto.me/files/drpy_js/怡萱动漫[漫].js" + }, + { + "key":"hipy_js_银河影视[V2]", + "name":"银河影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":595, + "ext":"https://hipy.trylyingto.me/files/drpy_js/银河影视[V2].js" + }, + { + "key":"hipy_js_小马影视[V2]", + "name":"小马影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":596, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小马影视[V2].js" + }, + { + "key":"hipy_js_0855影视", + "name":"0855影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":597, + "ext":"https://hipy.trylyingto.me/files/drpy_js/0855影视.js" + }, + { + "key":"hipy_js_河狸影视[V2]", + "name":"河狸影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":598, + "ext":"https://hipy.trylyingto.me/files/drpy_js/河狸影视[V2].js" + }, + { + "key":"hipy_js_柠檬影视[V2]", + "name":"柠檬影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":599, + "ext":"https://hipy.trylyingto.me/files/drpy_js/柠檬影视[V2].js" + }, + { + "key":"hipy_js_奇米动漫[漫]", + "name":"奇米动漫[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":600, + "ext":"https://hipy.trylyingto.me/files/drpy_js/奇米动漫[漫].js" + }, + { + "key":"hipy_js_鸭飞影视[V2]", + "name":"鸭飞影视[V2](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":601, + "ext":"https://hipy.trylyingto.me/files/drpy_js/鸭飞影视[V2].js" + }, + { + "key":"hipy_js_畅梦影视[优]", + "name":"畅梦影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":602, + "ext":"https://hipy.trylyingto.me/files/drpy_js/畅梦影视[优].js" + }, + { + "key":"hipy_js_剧巴巴", + "name":"剧巴巴(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":603, + "ext":"https://hipy.trylyingto.me/files/drpy_js/剧巴巴.js" + }, + { + "key":"hipy_js_蜥蜴影视[优]", + "name":"蜥蜴影视[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":604, + "ext":"https://hipy.trylyingto.me/files/drpy_js/蜥蜴影视[优].js" + }, + { + "key":"hipy_js_达达猪", + "name":"达达猪(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":605, + "ext":"https://hipy.trylyingto.me/files/drpy_js/达达猪.js" + }, + { + "key":"hipy_js_UAA[密]", + "name":"UAA[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":606, + "ext":"https://hipy.trylyingto.me/files/drpy_js/UAA[密].js" + }, + { + "key":"hipy_js_牌牌影院", + "name":"牌牌影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":607, + "ext":"https://hipy.trylyingto.me/files/drpy_js/牌牌影院.js" + }, + { + "key":"hipy_js_UAA[听]", + "name":"UAA[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":608, + "ext":"https://hipy.trylyingto.me/files/drpy_js/UAA[听].js" + }, + { + "key":"hipy_js_云电影网", + "name":"云电影网(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":609, + "ext":"https://hipy.trylyingto.me/files/drpy_js/云电影网.js" + }, + { + "key":"hipy_js_GO影视", + "name":"GO影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":610, + "ext":"https://hipy.trylyingto.me/files/drpy_js/GO影视.js" + }, + { + "key":"hipy_js_漫画走廊[画密飞]", + "name":"漫画走廊[画密飞](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":611, + "ext":"https://hipy.trylyingto.me/files/drpy_js/漫画走廊[画密飞].js" + }, + { + "key":"hipy_js_eFuns", + "name":"eFuns(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":612, + "ext":"https://hipy.trylyingto.me/files/drpy_js/eFuns.js" + }, + { + "key":"hipy_js_追影TV", + "name":"追影TV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":0, + "order_num":613, + "ext":"https://hipy.trylyingto.me/files/drpy_js/追影TV.js" + }, + { + "key":"hipy_js_金金虫", + "name":"金金虫(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":614, + "ext":"https://hipy.trylyingto.me/files/drpy_js/金金虫.js" + }, + { + "key":"hipy_js_极点影视", + "name":"极点影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":615, + "ext":"https://hipy.trylyingto.me/files/drpy_js/极点影视.js" + }, + { + "key":"hipy_js_泡泡影院", + "name":"泡泡影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":616, + "ext":"https://hipy.trylyingto.me/files/drpy_js/泡泡影院.js" + }, + { + "key":"hipy_js_青龙", + "name":"青龙(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":619, + "ext":"https://hipy.trylyingto.me/files/drpy_js/青龙.js" + }, + { + "key":"hipy_js_88tvs", + "name":"88tvs(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":620, + "ext":"https://hipy.trylyingto.me/files/drpy_js/88tvs.js" + }, + { + "key":"hipy_js_🎒📌央视大全[🎒] ", + "name":"🎒📌央视大全[🎒] (drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":621, + "ext":"https://hipy.trylyingto.me/files/drpy_js/🎒📌央视大全[🎒] .js" + }, + { + "key":"hipy_js_片多多[优]", + "name":"片多多[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":622, + "ext":"https://hipy.trylyingto.me/files/drpy_js/片多多[优].js" + }, + { + "key":"hipy_js_926tv[球]", + "name":"926tv[球](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":623, + "ext":"https://hipy.trylyingto.me/files/drpy_js/926tv[球].js" + }, + { + "key":"hipy_js_盘搜", + "name":"盘搜(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":624, + "ext":"https://hipy.trylyingto.me/files/drpy_js/盘搜.js" + }, + { + "key":"hipy_js_大全[央]", + "name":"大全[央](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":625, + "ext":"https://hipy.trylyingto.me/files/drpy_js/大全[央].js" + }, + { + "key":"hipy_js_老王电影[自动]", + "name":"老王电影[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":626, + "ext":"https://hipy.trylyingto.me/files/drpy_js/老王电影[自动].js" + }, + { + "key":"hipy_js_KimiVod", + "name":"KimiVod(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":627, + "ext":"https://hipy.trylyingto.me/files/drpy_js/KimiVod.js" + }, + { + "key":"hipy_js_素白白", + "name":"素白白(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":628, + "ext":"https://hipy.trylyingto.me/files/drpy_js/素白白.js" + }, + { + "key":"hipy_js_焱淼4kapp[优]", + "name":"焱淼4kapp[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":629, + "ext":"https://hipy.trylyingto.me/files/drpy_js/焱淼4kapp[优].js" + }, + { + "key":"hipy_js_木瓜影视", + "name":"木瓜影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":630, + "ext":"https://hipy.trylyingto.me/files/drpy_js/木瓜影视.js" + }, + { + "key":"hipy_js_熊猫TV", + "name":"熊猫TV(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":631, + "ext":"https://hipy.trylyingto.me/files/drpy_js/熊猫TV.js" + }, + { + "key":"hipy_js_全能影视", + "name":"全能影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":632, + "ext":"https://hipy.trylyingto.me/files/drpy_js/全能影视.js" + }, + { + "key":"hipy_js_忍者影视", + "name":"忍者影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":633, + "ext":"https://hipy.trylyingto.me/files/drpy_js/忍者影视.js" + }, + { + "key":"hipy_js_小猫电影院", + "name":"小猫电影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":634, + "ext":"https://hipy.trylyingto.me/files/drpy_js/小猫电影院.js" + }, + { + "key":"hipy_js_RJAV[密]", + "name":"RJAV[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":635, + "ext":"https://hipy.trylyingto.me/files/drpy_js/RJAV[密].js" + }, + { + "key":"hipy_js_HBOTV[优]", + "name":"HBOTV[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":636, + "ext":"https://hipy.trylyingto.me/files/drpy_js/HBOTV[优].js" + }, + { + "key":"hipy_js_电影兔", + "name":"电影兔(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":637, + "ext":"https://hipy.trylyingto.me/files/drpy_js/电影兔.js" + }, + { + "key":"hipy_js_libvio", + "name":"libvio(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":638, + "ext":"https://hipy.trylyingto.me/files/drpy_js/libvio.js" + }, + { + "key":"hipy_js_哔嘀影视", + "name":"哔嘀影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":639, + "ext":"https://hipy.trylyingto.me/files/drpy_js/哔嘀影视.js" + }, + { + "key":"hipy_js_歪片星球[资]", + "name":"歪片星球[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":640, + "ext":"https://hipy.trylyingto.me/files/drpy_js/歪片星球[资].js" + }, + { + "key":"hipy_js_央视少儿[漫]", + "name":"央视少儿[漫](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":641, + "ext":"https://hipy.trylyingto.me/files/drpy_js/央视少儿[漫].js" + }, + { + "key":"hipy_js_央视大全[优]", + "name":"央视大全[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":0, + "quickSearch":0, + "filterable":1, + "order_num":642, + "ext":"https://hipy.trylyingto.me/files/drpy_js/央视大全[优].js" + }, + { + "key":"hipy_js_墨点影视", + "name":"墨点影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":643, + "ext":"https://hipy.trylyingto.me/files/drpy_js/墨点影视.js" + }, + { + "key":"hipy_js_茶杯狐", + "name":"茶杯狐(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":644, + "ext":"https://hipy.trylyingto.me/files/drpy_js/茶杯狐.js" + }, + { + "key":"hipy_js_85k影视", + "name":"85k影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":645, + "ext":"https://hipy.trylyingto.me/files/drpy_js/85k影视.js" + }, + { + "key":"hipy_js_茄子影视[自动]", + "name":"茄子影视[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":646, + "ext":"https://hipy.trylyingto.me/files/drpy_js/茄子影视[自动].js" + }, + { + "key":"hipy_js_影梦影视", + "name":"影梦影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":647, + "ext":"https://hipy.trylyingto.me/files/drpy_js/影梦影视.js" + }, + { + "key":"hipy_js_南瓜影视", + "name":"南瓜影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":648, + "ext":"https://hipy.trylyingto.me/files/drpy_js/南瓜影视.js" + }, + { + "key":"hipy_js_🤡星芽短剧[🤡]", + "name":"🤡星芽短剧[🤡](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":649, + "ext":"https://hipy.trylyingto.me/files/drpy_js/🤡星芽短剧[🤡].js" + }, + { + "key":"hipy_js_绿茶", + "name":"绿茶(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":650, + "ext":"https://hipy.trylyingto.me/files/drpy_js/绿茶.js" + }, + { + "key":"hipy_js_rarbt(全)[优]", + "name":"rarbt(全)[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":651, + "ext":"https://hipy.trylyingto.me/files/drpy_js/rarbt(全)[优].js" + }, + { + "key":"hipy_js_妖狐影视[自动]", + "name":"妖狐影视[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":652, + "ext":"https://hipy.trylyingto.me/files/drpy_js/妖狐影视[自动].js" + }, + { + "key":"hipy_js_cally66影视", + "name":"cally66影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":653, + "ext":"https://hipy.trylyingto.me/files/drpy_js/cally66影视.js" + }, + { + "key":"hipy_js_rarbt[优]", + "name":"rarbt[优](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":654, + "ext":"https://hipy.trylyingto.me/files/drpy_js/rarbt[优].js" + }, + { + "key":"hipy_js_泥巴影院", + "name":"泥巴影院(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":655, + "ext":"https://hipy.trylyingto.me/files/drpy_js/泥巴影院.js" + }, + { + "key":"hipy_js_电影猎手[自动]", + "name":"电影猎手[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":656, + "ext":"https://hipy.trylyingto.me/files/drpy_js/电影猎手[自动].js" + }, + { + "key":"hipy_js_多多影音", + "name":"多多影音(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":657, + "ext":"https://hipy.trylyingto.me/files/drpy_js/多多影音.js" + }, + { + "key":"hipy_js_木偶哥哥[盘]", + "name":"木偶哥哥[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":658, + "ext":"https://hipy.trylyingto.me/files/drpy_js/木偶哥哥[盘].js" + }, + { + "key":"hipy_js_嗷呜动漫", + "name":"嗷呜动漫(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":659, + "ext":"https://hipy.trylyingto.me/files/drpy_js/嗷呜动漫.js" + }, + { + "key":"hipy_js_美剧窝", + "name":"美剧窝(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":660, + "ext":"https://hipy.trylyingto.me/files/drpy_js/美剧窝.js" + }, + { + "key":"hipy_js_Auete影视", + "name":"Auete影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":661, + "ext":"https://hipy.trylyingto.me/files/drpy_js/Auete影视.js" + }, + { + "key":"hipy_js_[密]RjAv", + "name":"[密]RjAv(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":662, + "ext":"https://hipy.trylyingto.me/files/drpy_js/[密]RjAv.js" + }, + { + "key":"hipy_js_努努影院1", + "name":"努努影院1(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":663, + "ext":"https://hipy.trylyingto.me/files/drpy_js/努努影院1.js" + }, + { + "key":"hipy_js_111tv[自动]", + "name":"111tv[自动](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":664, + "ext":"https://hipy.trylyingto.me/files/drpy_js/111tv[自动].js" + }, + { + "key":"hipy_js_泥视频[资]", + "name":"泥视频[资](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":665, + "ext":"https://hipy.trylyingto.me/files/drpy_js/泥视频[资].js" + }, + { + "key":"hipy_js_222听书[听]", + "name":"222听书[听](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":666, + "ext":"https://hipy.trylyingto.me/files/drpy_js/222听书[听].js" + }, + { + "key":"hipy_js_一个g影视", + "name":"一个g影视(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":667, + "ext":"https://hipy.trylyingto.me/files/drpy_js/一个g影视.js" + }, + { + "key":"hipy_js_泽少1", + "name":"泽少1(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":668, + "ext":"https://hipy.trylyingto.me/files/drpy_js/APPV2[模板].js?type=url¶ms=http://122.228.85.203:1000" + }, + { + "key":"hipy_js_泽少2", + "name":"泽少2(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "order_num":668, + "ext":"https://hipy.trylyingto.me/files/drpy_js/APPV2[模板].js?type=url¶ms=http://122.228.85.203:1000" + }, + { + "key":"hipy_js_夸克分享[盘]", + "name":"夸克分享[盘](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":669, + "ext":"https://hipy.trylyingto.me/files/drpy_js/夸克分享[合].js?type=url¶ms=../json/夸克分享.json" + }, + { + "key":"hipy_js_夸克分享2[合]", + "name":"夸克分享2[合](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":672, + "ext":"https://hipy.trylyingto.me/files/drpy_js/夸克分享2[合].js" + }, + { + "key":"hipy_js_xvideos涩涩[密]", + "name":"xvideos涩涩[密](drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":0, + "order_num":673, + "ext":"https://hipy.trylyingto.me/files/drpy_js/xvideos涩涩[密].js" + }, + { + "key":"hipy_js_农民影视gz", + "name":"农民影视gz(drpy_t3)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":1, + "quickSearch":0, + "filterable":1, + "order_num":675, + "ext":"https://hipy.trylyingto.me/files/drpy_js/农民影视gz.js" + }, + { + "key":"Test_jsapi", + "name":"Test_jsapi(drpy)", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/drpy2.min.js", + "searchable":2, + "quickSearch":0, + "filterable":0, + "ext":"https://hipy.trylyingto.me/files/txt/js/jsapi.js", + "jar":"https://hipy.trylyingto.me/files/jar/custom_jsapi.jar", + "order_num":9999 + }, + { + "key":"js_origin", + "name":"JS(原始)", + "type":3, + "api":"https://hipy.trylyingto.me/files/txt/js/原始JS.js", + "searchable":1, + "quickSearch":1, + "filterable":1, + "ext":"", + "order_num":9999 + }, + { + "key":"CMS_非凡资源", + "name":"🥗┃非凡┃资源", + "type":1, + "api":"http://cj.ffzyapi.com/api.php/provide/vod/", + "playurl":"json:http://jx.84jia.com/m3u8ts.php?url=", + "searchable":1, + "quickSearch":1, + "filterable":1, + "categories":[ + "动作片", + "喜剧片", + "科幻片", + "恐怖片", + "爱情片", + "剧情片", + "战争片", + "记录片", + "国产剧", + "欧美剧", + "香港剧", + "韩国剧", + "台湾剧", + "日本剧", + "海外剧", + "泰国剧", + "国产动漫", + "日韩动漫", + "欧美动漫", + "港台动漫", + "海外动漫", + "大陆综艺", + "港台综艺", + "日韩综艺", + "欧美综艺" + ], + "order_num":9999 + }, + { + "key":"CMS_量子资源", + "name":"🥑┃量子┃资源", + "type":1, + "api":"http://cj.lziapi.com/api.php/provide/vod/", + "playurl":"json:http://jx.84jia.com/m3u8ts.php?url=", + "searchable":1, + "quickSearch":1, + "filterable":0, + "categories":[ + "国产剧", + "国产动漫", + "泰国剧", + "台湾剧", + "香港剧", + "欧美剧", + "韩国剧", + "日本剧", + "动漫", + "体育", + "剧情片", + "动作片", + "爱情片", + "喜剧片" + ], + "order_num":9999 + }, + { + "key":"CMS_索尼资源", + "name":"🥑┃索尼┃资源", + "type":1, + "api":"https://suoniapi.com/api.php/provide/vod/from/snm3u8/", + "categories":[ + "动作片", + "喜剧片", + "爱情片", + "科幻片", + "恐怖片", + "剧情片", + "战争片", + "国产剧", + "欧美剧", + "韩剧", + "日剧", + "港剧", + "台剧", + "泰剧", + "海外剧", + "纪录片", + "大陆综艺", + "日韩综艺", + "港台综艺", + "欧美综艺", + "国产动漫", + "日韩动漫", + "欧美动漫", + "动画片", + "港台动漫", + "海外动漫", + "演唱会", + "体育赛事", + "篮球", + "足球", + "预告片", + "斯诺克", + "影视解说" + ], + "searchable":1, + "quickSearch":1, + "order_num":9999 + }, + { + "key":"CMS_爱酷秒", + "name":"🥑┃酷秒┃资源", + "type":0, + "api":"http://caiji.ikum.cc:8099/api.php/provide/vod/at/xml", + "categories":[ + "电影", + "连续剧", + "综艺", + "动漫", + "国产剧", + "港台剧", + "日韩剧", + "欧美剧", + "其他剧", + "儿童", + "蓝光", + "哔哩哔哩" + ], + "searchable":1, + "quickSearch":1, + "order_num":9999 + }, + { + "key":"Live2Cms", + "name":"直播转点播V2", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/live2cms.js", + "searchable":2, + "quickSearch":0, + "filterable":0, + "ext":"https://hipy.trylyingto.me/files/json/live2mv_data.json", + "order_num":9999 + }, + { + "key":"Alist", + "name":"Alist", + "type":3, + "api":"https://hipy.trylyingto.me/files/drpy_libs/alist.min.js", + "searchable":2, + "quickSearch":0, + "filterable":0, + "ext":"https://hipy.trylyingto.me/files/json/alist.json;200;video", + "order_num":9999 + }, + { + "key":"网盘配置", + "name":"网盘及彈幕配置", + "type":3, + "api":"csp_Config", + "searchable":0, + "changeable":0, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "order_num":9999 + }, + { + "key":"Local", + "name":"本地", + "type":3, + "api":"csp_Local", + "order_num":9999 + }, + { + "key":"PushShare", + "name":"我的资源分享", + "type":3, + "api":"csp_PushShare", + "searchable":1, + "quickSearch":1, + "changeable":1, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/pushshare.txt$$$db$$$1", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"AliShare", + "name":"阿里云盘影视分享", + "type":3, + "api":"csp_AliShare", + "searchable":1, + "quickSearch":1, + "changeable":1, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/alishare.txt$$$db$$$1", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"AliShareEBook", + "name":"阿里云盘书籍分享", + "type":3, + "api":"csp_AliShare", + "searchable":0, + "quickSearch":0, + "changeable":0, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/alishare.ebook.txt$$$db$$$1", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"QuarkShare", + "name":"夸克云盘分享", + "type":3, + "api":"csp_QuarkShare", + "searchable":0, + "quickSearch":0, + "changeable":0, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/quarkshare.txt", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"ThunderShare", + "name":"迅雷云盘分享", + "type":3, + "api":"csp_ThunderShare", + "searchable":0, + "quickSearch":0, + "changeable":0, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/thundershare.txt", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"PikPakShare", + "name":"PikPak分享", + "type":3, + "api":"csp_PikPakShare", + "searchable":1, + "quickSearch":1, + "changeable":1, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/pikpakclass.json$$$https://hipy.trylyingto.me/files/json/pikpakclass.json.txt.gz", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"SambaShare", + "name":"Samba分享", + "type":3, + "api":"csp_SambaShare", + "searchable":0, + "quickSearch":0, + "changeable":0, + "filterable":0, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://hipy.trylyingto.me/files/json/sambashare.txt", + "order_num":9999 + }, + { + "key":"Wogg", + "name":"玩偶哥哥|网盘", + "type":3, + "api":"csp_Wogg", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://wogg.link/$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/wogg.json", + "order_num":9999 + }, + { + "key":"Wo4k", + "name":"玩偶4K|磁力", + "type":3, + "api":"csp_Wo4k", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://www.wo4k.net/$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/wogg.json", + "order_num":9999 + }, + { + "key":"Wobg", + "name":"玩偶表哥|网盘", + "type":3, + "api":"csp_Wobg", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://wobge.run.goorm.io$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/wogg.json", + "order_num":9999 + }, + { + "key":"yydsys", + "name":"玩你老哥|网盘", + "type":3, + "api":"csp_Wobg", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://tv.yydsys.top/$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/wogg.json", + "order_num":9999 + }, + { + "key":"Xinshijue", + "name":"新视觉|网盘", + "type":3, + "api":"csp_Xinshijue", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://www.80yy3.com/$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/wogg.json", + "order_num":9999 + }, + { + "key":"Moli", + "name":"HDmoli|网盘", + "type":3, + "api":"csp_Moli", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://www.hdmoli.pro/$$$proxy$$$1$$$https://hipy.trylyingto.me/files/json/moli.json", + "order_num":9999 + }, + { + "key":"Hdhive", + "name":"影巢|网盘", + "type":3, + "api":"csp_Hdhive", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"Ppxzy", + "name":"皮皮虾|网盘", + "type":3, + "api":"csp_Ppxzy", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://ppxzy.ink$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"校长影视", + "name":"校长影视|网盘", + "type":3, + "api":"csp_XiaoZhang", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://xzyshd.com$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"Bdys_spider", + "name":"哔滴┃磁力", + "api":"csp_Bdys01", + "type":3, + "filterable":1, + "searchable":1, + "quickSearch":1, + "ext":"https://www.yjys.me/$$$None$$$1", + "order_num":9999 + }, + { + "key":"YunPan", + "name":"云盘资源|网盘", + "type":3, + "api":"csp_YunPan", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$None$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"YingSo", + "name":"影搜|网盘搜索", + "type":3, + "api":"csp_YingSo", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "order_num":9999 + }, + { + "key":"混合盘", + "name":"混合盘|网盘搜索", + "type":3, + "api":"csp_HunHePan", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"88Pan", + "name":"88网盘|网盘搜索", + "type":3, + "api":"csp_EightEight", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://662688.xyz$$$", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"PanSearch", + "name":"PanSearch|网盘搜索", + "type":3, + "api":"csp_PanSearch", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "order_num":9999 + }, + { + "key":"盘友圈", + "name":"盘友圈|网盘搜索", + "type":3, + "api":"csp_Panyq", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$site$$$proxy", + "order_num":9999 + }, + { + "key":"秒搜", + "name":"秒搜|网盘搜索", + "type":3, + "api":"csp_MiaoSou", + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"Funletu", + "name":"趣盘搜|夸克搜索", + "type":3, + "api":"csp_Funletu", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"QuarkPanso", + "name":"夸克盘搜|夸克搜索", + "type":3, + "api":"csp_QuarkPanso", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"DaPanSo", + "name":"大盘搜|网盘搜索", + "type":3, + "api":"csp_DaPanSo", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://dapanso.com$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"PikaSo", + "name":"皮卡搜|网盘搜索", + "type":3, + "api":"csp_PikaSo", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://www.pikaso.top/$$$None", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"Qianfan", + "name":"千帆|网盘搜索", + "type":3, + "api":"csp_Qianfan", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://pan.qianfan.app$$$None$$$https://hipy.trylyingto.me/files/json/qianfan.txt$$$1", + "order_num":9999 + }, + { + "key":"YunSo", + "name":"小云搜索|网盘搜索", + "type":3, + "api":"csp_YunSo", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$https://www.yunso.net$$$None$$$1", + "style":{ + "type":"list", + "ratio":1.1 + }, + "order_num":9999 + }, + { + "key":"YunPanOne", + "name":"云盘One|网盘搜索", + "type":3, + "api":"csp_YunPanOne", + "quickSearch":1, + "changeable":1, + "filterable":1, + "timeout":60, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json$$$None$$$proxy$$$1", + "order_num":9999 + }, + { + "key":"MV_vod", + "name":"电视┃MTV", + "type":1, + "api":"https://mv.wogg.link/mv/vod", + "searchable":1, + "quickSearch":0, + "changeable":0, + "order_num":9999 + }, + { + "key":"酷狗", + "name":"酷狗", + "type":3, + "api":"csp_Kugou", + "searchable":1, + "changeable":0, + "order_num":9999 + }, + { + "key":"Iktv", + "name":"KTV", + "type":3, + "api":"csp_Iktv", + "searchable":1, + "changeable":0, + "order_num":9999 + }, + { + "key":"Yinyuetai", + "name":"音悦台", + "type":3, + "api":"csp_Yinyuetai", + "searchable":1, + "changeable":0, + "order_num":9999 + }, + { + "key":"push_agent", + "name":"推送", + "type":3, + "api":"csp_Push", + "changeable":0, + "timeout":30, + "ext":"https://hipy.trylyingto.me/files/json/tokenm.json", + "order_num":9999 + }, + { + "key":"應用商店", + "name":"應用商店", + "type":3, + "api":"csp_Market", + "searchable":0, + "changeable":0, + "ext":"https://fm.t4tv.hz.cz/json/market.json", + "order_num":9999 + } + ], + "parses":[ + { + "name":"🌐Ⓤ", + "type":0, + "url":"", + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"轮询", + "type":2, + "url":"Sequence", + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"并发", + "type":2, + "url":"Parallel", + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"777", + "url":"https://jx.777jiexi.com/player/?url=", + "type":0, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"哔哩", + "url":"https://hipy.trylyingto.me/parse/api/哔哩.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"杰森", + "url":"https://hipy.trylyingto.me/parse/api/杰森.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"阳途", + "url":"https://hipy.trylyingto.me/parse/api/阳途.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"夜幕", + "url":"https://hipy.trylyingto.me/parse/api/夜幕.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"虾米", + "url":"https://hipy.trylyingto.me/parse/api/虾米.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"CK", + "url":"https://hipy.trylyingto.me/parse/api/CK.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"盘古", + "url":"https://hipy.trylyingto.me/parse/api/盘古.js?url=", + "type":1, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"8090g", + "url":"https://www.8090g.cn/jiexi/?url=", + "type":0, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"小7", + "url":"https://2.08bk.com/?url=", + "type":0, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"BL解析", + "url":"https://vip.bljiex.com/?v=", + "type":0, + "ext":{ + "flag":[ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header":{ + "User-Agent":"Mozilla/5.0" + } + }, + { + "name":"keyu", + "type":1, + "url":"http://newjiexi.gotka.top/keyu3.php?url=", + "ext":{ + "flag":[ + "qq", + "腾讯", + "qiyi", + "爱奇艺", + "奇艺", + "youku", + "优酷", + "mgtv", + "芒果", + "letv", + "乐视", + "pptv", + "PPTV", + "sohu", + "bilibili", + "哔哩哔哩", + "哔哩" + ], + "header":{ + "User-Agent":"okhttp/4.1.0" + } + }, + "order_num":9999 + } + ], + "flags":[ + "imgo", + "youku", + "qq", + "qq 预告及花絮", + "iqiyi", + "qiyi", + "fun", + "letv", + "leshi", + "sohu", + "tudou", + "xigua", + "cntv", + "1905", + "pptv", + "mgtv", + "wasu", + "bilibili", + "renrenmi" + ], + "hotSearch":[ + { + "name":"mobilesearch", + "request":{ + "method":"GET", + "header":[ + { + "key":"Referer", + "value":"https://hipy.trylyingto.me" + } + ], + "url":{ + "raw":"https://hipy.trylyingto.me/hotsugg?t={time}" + } + }, + "response":{ + "result":"$.data", + "data":[ + { + "key":"keyword", + "value":"title" + } + ] + } + } + ], + "lives":[ + { + "name":"直播", + "type":0, + "url":"https://hipy.trylyingto.me/files/txt/mytv.txt", + "playerType":1, + "ua":"okhttp/3.12.13", + "epg":"https://epg.mxdyeah.top/api/diyp/?ch={name}&date={date}", + "logo":"https://live.mxdyeah.top/logo/{name}.png" + } + ], + "sniffer":{ + "userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27", + "isVideoFormat":"http((?!http).){26,}\\.(m3u8|mp4|flv|avi|mkv|wmv|mpg|mpeg|mov|ts|3gp|rm|rmvb|asf|m4a|mp3|wma)", + "custom":[ + { + "url":"/Cloud/Down/AliCloud/", + "mimeType":"text/html", + "encoding":"utf-8", + "header":{ + "Referer":"https://zxzj.vip/" + } + }, + { + "url":"ysting.ysxs8.vip", + "mimeType":"text/html", + "encoding":"utf-8", + "header":{ + "Referer":"http://ysting.ysxs8.vip:81" + } + } + ] + }, + "recommend":[ + { + "name":"豆瓣推荐", + "request":{ + "method":"GET", + "header":[ + { + "key":"Referer", + "value":"https://movie.douban.com/" + } + ], + "url":{ + "raw":"https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&playable=1&start=0&year_range=2022,2022" + } + }, + "response":{ + "result":"$.data", + "data":[ + { + "key":"name", + "value":"title" + }, + { + "key":"note", + "value":"rate" + }, + { + "key":"pic", + "value":"cover" + } + ] + }, + "expires":"86400" + } + ], + "rating":[ + { + "name":"rating", + "request":{ + "method":"GET", + "url":{ + "raw":"https://api.wmdb.tv/api/v1/movie/search?q={name}&limit=1" + } + }, + "response":{ + "result":"this", + "data":[ + { + "key":"rating", + "value":"doubanRating" + } + ] + } + } + ], + "doh":[ + { + "ips":[ + "8.8.4.4", + "8.8.8.8" + ], + "name":"Google", + "url":"https://dns.google/dns-query" + }, + { + "ips":[ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::1111", + "2606:4700:4700::1001" + ], + "name":"Cloudflare", + "url":"https://cloudflare-dns.com/dns-query" + }, + { + "ips":[ + "94.140.14.140", + "94.140.14.141" + ], + "name":"AdGuard", + "url":"https://dns.adguard.com/dns-query" + }, + { + "ips":[ + "84.200.69.80", + "84.200.70.40" + ], + "name":"DNSWatch", + "url":"https://resolver2.dns.watch/dns-query" + }, + { + "ips":[ + "9.9.9.9", + "149.112.112.112" + ], + "name":"Quad9", + "url":"https://dns.quad9.net/dns-quer" + } + ], + "ijk":[ + { + "group":"软解码", + "options":[ + { + "category":4, + "name":"opensles", + "value":"0" + }, + { + "category":4, + "name":"overlay-format", + "value":"842225234" + }, + { + "category":4, + "name":"framedrop", + "value":"1" + }, + { + "category":4, + "name":"soundtouch", + "value":"1" + }, + { + "category":4, + "name":"start-on-prepared", + "value":"1" + }, + { + "category":1, + "name":"http-detect-range-support", + "value":"0" + }, + { + "category":1, + "name":"fflags", + "value":"fastseek" + }, + { + "category":2, + "name":"skip_loop_filter", + "value":"48" + }, + { + "category":4, + "name":"reconnect", + "value":"1" + }, + { + "category":4, + "name":"enable-accurate-seek", + "value":"0" + }, + { + "category":4, + "name":"mediacodec", + "value":"0" + }, + { + "category":4, + "name":"mediacodec-auto-rotate", + "value":"0" + }, + { + "category":4, + "name":"mediacodec-handle-resolution-change", + "value":"0" + }, + { + "category":4, + "name":"mediacodec-hevc", + "value":"0" + }, + { + "category":1, + "name":"dns_cache_timeout", + "value":"600000000" + }, + { + "category":4, + "name":"max-buffer-size", + "value":"15728640" + } + ] + }, + { + "group":"硬解码", + "options":[ + { + "category":4, + "name":"opensles", + "value":"0" + }, + { + "category":4, + "name":"overlay-format", + "value":"842225234" + }, + { + "category":4, + "name":"framedrop", + "value":"1" + }, + { + "category":4, + "name":"soundtouch", + "value":"1" + }, + { + "category":4, + "name":"start-on-prepared", + "value":"1" + }, + { + "category":1, + "name":"http-detect-range-support", + "value":"0" + }, + { + "category":1, + "name":"fflags", + "value":"fastseek" + }, + { + "category":2, + "name":"skip_loop_filter", + "value":"48" + }, + { + "category":4, + "name":"reconnect", + "value":"1" + }, + { + "category":4, + "name":"max-buffer-size", + "value":"15728640" + }, + { + "category":4, + "name":"enable-accurate-seek", + "value":"0" + }, + { + "category":4, + "name":"mediacodec", + "value":"1" + }, + { + "category":4, + "name":"mediacodec-auto-rotate", + "value":"1" + }, + { + "category":4, + "name":"mediacodec-handle-resolution-change", + "value":"1" + }, + { + "category":4, + "name":"mediacodec-hevc", + "value":"1" + }, + { + "category":1, + "name":"dns_cache_timeout", + "value":"600000000" + } + ] + } + ], + "rules":[ + { + "hosts":[ + "raw.githubusercontent.com", + "googlevideo.com", + "cdn.v82u1l.com", + "cdn.iz8qkg.com", + "cdn.kin6c1.com", + "c.biggggg.com", + "c.olddddd.com", + "haiwaikan.com", + "www.histar.tv", + "youtube.com", + "uhibo.com", + ".*boku.*", + ".*nivod.*", + ".*ulivetv.*", + "wogg.link", + "wogg.xyz" + ], + "name":"proxy" + }, + { + "hosts":[ + "magnet" + ], + "name":"cl", + "regex":[ + "最新", + "直播", + "更新" + ] + }, + { + "hosts":[ + "haiwaikan" + ], + "name":"海外看", + "regex":[ + "10.0099", + "10.3333", + "16.0599", + "8.1748", + "12.33", + "10.85" + ] + }, + { + "hosts":[ + "suonizy" + ], + "name":"索尼", + "regex":[ + "15.1666", + "15.2666" + ] + }, + { + "hosts":[ + "bfzy", + "bfbfvip" + ], + "name":"暴風", + "regex":[ + "#EXTINF.*?\\s+.*?adjump.*?\\.ts\\s+" + ] + }, + { + "hosts":[ + "aws.ulivetv.net" + ], + "name":"星星", + "regex":[ + "#EXT-X-DISCONTINUITY\\r*\\n*#EXTINF:8,[\\s\\S]*?#EXT-X-DISCONTINUITY" + ] + }, + { + "hosts":[ + "vip.lz", + "hd.lz", + "v.cdnlz" + ], + "name":"量子", + "regex":[ + "18.5333" + ] + }, + { + "hosts":[ + "vip.ffzy", + "hd.ffzy" + ], + "name":"非凡", + "regex":[ + "25.1999", + "25.0666", + "25.08", + "20.52" + ] + }, + { + "hosts":[ + "huoshan.com" + ], + "name":"火山嗅探", + "regex":[ + "item_id=" + ] + }, + { + "hosts":[ + "douyin.com" + ], + "name":"抖音嗅探", + "regex":[ + "is_play_url=" + ] + }, + { + "hosts":[ + "api.52wyb.com" + ], + "name":"七新嗅探", + "regex":[ + "m3u8?pt=m3u8" + ] + }, + { + "hosts":[ + "10086.cn" + ], + "name":"czzy", + "regex":[ + "/storageWeb/servlet/downloadServlet" + ] + }, + { + "exclude":[ + ".m3u8" + ], + "hosts":[ + "bytetos.com", + "byteimg.com", + "bytednsdoc.com", + "pstatp.com" + ], + "name":"bdys", + "regex":[ + "/tos-cn" + ] + }, + { + "exclude":[ + ".m3u8" + ], + "hosts":[ + "bdys10.com" + ], + "name":"bdys10", + "regex":[ + "/obj/" + ] + } + ], + "ads":[ + "mimg.0c1q0l.cn", + "www.googletagmanager.com", + "www.google-analytics.com", + "mc.usihnbcq.cn", + "mg.g1mm3d.cn", + "mscs.svaeuzh.cn", + "cnzz.hhttm.top", + "tp.vinuxhome.com", + "cnzz.mmstat.com", + "www.baihuillq.com", + "s23.cnzz.com", + "z3.cnzz.com", + "c.cnzz.com", + "stj.v1vo.top", + "z12.cnzz.com", + "img.mosflower.cn", + "tips.gamevvip.com", + "ehwe.yhdtns.com", + "xdn.cqqc3.com", + "www.jixunkyy.cn", + "sp.chemacid.cn", + "hm.baidu.com", + "s9.cnzz.com", + "z6.cnzz.com", + "um.cavuc.com", + "mav.mavuz.com", + "wofwk.aoidf3.com", + "z5.cnzz.com", + "xc.hubeijieshikj.cn", + "tj.tianwenhu.com", + "xg.gars57.cn", + "k.jinxiuzhilv.com", + "cdn.bootcss.com", + "ppl.xunzhuo123.com", + "xomk.jiangjunmh.top", + "img.xunzhuo123.com", + "z1.cnzz.com", + "s13.cnzz.com", + "xg.huataisangao.cn", + "z7.cnzz.com", + "xg.huataisangao.cn", + "z2.cnzz.com", + "s96.cnzz.com", + "q11.cnzz.com", + "thy.dacedsfa.cn", + "xg.whsbpw.cn", + "s19.cnzz.com", + "z8.cnzz.com", + "s4.cnzz.com", + "f5w.as12df.top", + "ae01.alicdn.com", + "www.92424.cn", + "k.wudejia.com", + "vivovip.mmszxc.top", + "qiu.xixiqiu.com", + "cdnjs.hnfenxun.com", + "cms.qdwght.com", + "wan.51img1.com", + "iqiyi.hbuioo.com", + "vip.ffzyad.com", + "https://lf1-cdn-tos.bytegoofy.com/obj/tos-cn-i-dy/455ccf9e8ae744378118e4bd289288dd" + ], + "drives":[ + { + "name":"阿里", + "password":"43886374072944a2bcc55a0ed129ab48", + "type":"alidrive" + } + ], + "cost_time":477.9 + }, + "home": {"class":[{"type_id":"1","type_name":"电影"},{"type_id":"2","type_name":"连续剧"},{"type_id":"3","type_name":"综艺"},{"type_id":"4","type_name":"动漫"},{"type_id":"26","type_name":"短剧"}],"filters":{"1":[{"key":"cateId","name":"类型","value":[{"n":"全部","v":"1"},{"n":"动作片","v":"5"},{"n":"喜剧片","v":"6"},{"n":"爱情片","v":"7"},{"n":"科幻片","v":"8"},{"n":"恐怖片","v":"9"},{"n":"剧情片","v":"10"},{"n":"战争片","v":"11"},{"n":"惊悚片","v":"16"},{"n":"奇幻片","v":"17"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"},{"n":"泰国","v":"泰国"},{"n":"新加坡","v":"新加坡"},{"n":"马来西亚","v":"马来西亚"},{"n":"印度","v":"印度"},{"n":"英国","v":"英国"},{"n":"法国","v":"法国"},{"n":"加拿大","v":"加拿大"},{"n":"西班牙","v":"西班牙"},{"n":"俄罗斯","v":"俄罗斯"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年代","value":[{"n":"全部","v":""},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"},{"n":"1996","v":"1996"},{"n":"1995","v":"1995"},{"n":"1994","v":"1994"},{"n":"1993","v":"1993"},{"n":"1992","v":"1992"},{"n":"1991","v":"1991"},{"n":"1990","v":"1990"},{"n":"1989","v":"1989"},{"n":"1988","v":"1988"},{"n":"1987","v":"1987"},{"n":"1986","v":"1986"},{"n":"1985","v":"1985"},{"n":"1984","v":"1984"},{"n":"1983","v":"1983"},{"n":"1982","v":"1982"},{"n":"1981","v":"1981"},{"n":"1980","v":"1980"},{"n":"1979","v":"1979"},{"n":"1978","v":"1978"},{"n":"1977","v":"1977"},{"n":"1976","v":"1976"},{"n":"1975","v":"1975"},{"n":"1974","v":"1974"},{"n":"1973","v":"1973"},{"n":"1972","v":"1972"},{"n":"1971","v":"1971"},{"n":"1970","v":"1970"},{"n":"1969","v":"1969"},{"n":"1968","v":"1968"},{"n":"1967","v":"1967"},{"n":"1966","v":"1966"},{"n":"1965","v":"1965"},{"n":"1964","v":"1964"},{"n":"1963","v":"1963"},{"n":"1962","v":"1962"},{"n":"1961","v":"1961"},{"n":"1960","v":"1960"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}],"2":[{"key":"cateId","name":"类型","value":[{"n":"全部","v":"2"},{"n":"国产剧","v":"12"},{"n":"港台泰","v":"13"},{"n":"日韩剧","v":"14"},{"n":"欧美剧","v":"15"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"},{"n":"泰国","v":"泰国"},{"n":"新加坡","v":"新加坡"},{"n":"马来西亚","v":"马来西亚"},{"n":"印度","v":"印度"},{"n":"英国","v":"英国"},{"n":"法国","v":"法国"},{"n":"加拿大","v":"加拿大"},{"n":"西班牙","v":"西班牙"},{"n":"俄罗斯","v":"俄罗斯"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年代","value":[{"n":"全部","v":""},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"},{"n":"1996","v":"1996"},{"n":"1995","v":"1995"},{"n":"1994","v":"1994"},{"n":"1993","v":"1993"},{"n":"1992","v":"1992"},{"n":"1991","v":"1991"},{"n":"1990","v":"1990"},{"n":"1989","v":"1989"},{"n":"1988","v":"1988"},{"n":"1987","v":"1987"},{"n":"1986","v":"1986"},{"n":"1985","v":"1985"},{"n":"1984","v":"1984"},{"n":"1983","v":"1983"},{"n":"1982","v":"1982"},{"n":"1981","v":"1981"},{"n":"1980","v":"1980"},{"n":"1979","v":"1979"},{"n":"1978","v":"1978"},{"n":"1977","v":"1977"},{"n":"1976","v":"1976"},{"n":"1975","v":"1975"},{"n":"1974","v":"1974"},{"n":"1973","v":"1973"},{"n":"1972","v":"1972"},{"n":"1971","v":"1971"},{"n":"1970","v":"1970"},{"n":"1969","v":"1969"},{"n":"1968","v":"1968"},{"n":"1967","v":"1967"},{"n":"1966","v":"1966"},{"n":"1965","v":"1965"},{"n":"1964","v":"1964"},{"n":"1963","v":"1963"},{"n":"1962","v":"1962"},{"n":"1961","v":"1961"},{"n":"1960","v":"1960"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}],"3":[{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"},{"n":"泰国","v":"泰国"},{"n":"新加坡","v":"新加坡"},{"n":"马来西亚","v":"马来西亚"},{"n":"印度","v":"印度"},{"n":"英国","v":"英国"},{"n":"法国","v":"法国"},{"n":"加拿大","v":"加拿大"},{"n":"西班牙","v":"西班牙"},{"n":"俄罗斯","v":"俄罗斯"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年代","value":[{"n":"全部","v":""},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"},{"n":"1996","v":"1996"},{"n":"1995","v":"1995"},{"n":"1994","v":"1994"},{"n":"1993","v":"1993"},{"n":"1992","v":"1992"},{"n":"1991","v":"1991"},{"n":"1990","v":"1990"},{"n":"1989","v":"1989"},{"n":"1988","v":"1988"},{"n":"1987","v":"1987"},{"n":"1986","v":"1986"},{"n":"1985","v":"1985"},{"n":"1984","v":"1984"},{"n":"1983","v":"1983"},{"n":"1982","v":"1982"},{"n":"1981","v":"1981"},{"n":"1980","v":"1980"},{"n":"1979","v":"1979"},{"n":"1978","v":"1978"},{"n":"1977","v":"1977"},{"n":"1976","v":"1976"},{"n":"1975","v":"1975"},{"n":"1974","v":"1974"},{"n":"1973","v":"1973"},{"n":"1972","v":"1972"},{"n":"1971","v":"1971"},{"n":"1970","v":"1970"},{"n":"1969","v":"1969"},{"n":"1968","v":"1968"},{"n":"1967","v":"1967"},{"n":"1966","v":"1966"},{"n":"1965","v":"1965"},{"n":"1964","v":"1964"},{"n":"1963","v":"1963"},{"n":"1962","v":"1962"},{"n":"1961","v":"1961"},{"n":"1960","v":"1960"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}],"4":[{"key":"cateId","name":"类型","value":[{"n":"全部","v":"4"},{"n":"动漫剧","v":"18"},{"n":"动漫片","v":"19"}]},{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"},{"n":"泰国","v":"泰国"},{"n":"新加坡","v":"新加坡"},{"n":"马来西亚","v":"马来西亚"},{"n":"印度","v":"印度"},{"n":"英国","v":"英国"},{"n":"法国","v":"法国"},{"n":"加拿大","v":"加拿大"},{"n":"西班牙","v":"西班牙"},{"n":"俄罗斯","v":"俄罗斯"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年代","value":[{"n":"全部","v":""},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"},{"n":"1996","v":"1996"},{"n":"1995","v":"1995"},{"n":"1994","v":"1994"},{"n":"1993","v":"1993"},{"n":"1992","v":"1992"},{"n":"1991","v":"1991"},{"n":"1990","v":"1990"},{"n":"1989","v":"1989"},{"n":"1988","v":"1988"},{"n":"1987","v":"1987"},{"n":"1986","v":"1986"},{"n":"1985","v":"1985"},{"n":"1984","v":"1984"},{"n":"1983","v":"1983"},{"n":"1982","v":"1982"},{"n":"1981","v":"1981"},{"n":"1980","v":"1980"},{"n":"1979","v":"1979"},{"n":"1978","v":"1978"},{"n":"1977","v":"1977"},{"n":"1976","v":"1976"},{"n":"1975","v":"1975"},{"n":"1974","v":"1974"},{"n":"1973","v":"1973"},{"n":"1972","v":"1972"},{"n":"1971","v":"1971"},{"n":"1970","v":"1970"},{"n":"1969","v":"1969"},{"n":"1968","v":"1968"},{"n":"1967","v":"1967"},{"n":"1966","v":"1966"},{"n":"1965","v":"1965"},{"n":"1964","v":"1964"},{"n":"1963","v":"1963"},{"n":"1962","v":"1962"},{"n":"1961","v":"1961"},{"n":"1960","v":"1960"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}],"26":[{"key":"area","name":"地区","value":[{"n":"全部","v":""},{"n":"大陆","v":"大陆"},{"n":"香港","v":"香港"},{"n":"台湾","v":"台湾"},{"n":"美国","v":"美国"},{"n":"韩国","v":"韩国"},{"n":"日本","v":"日本"},{"n":"泰国","v":"泰国"},{"n":"新加坡","v":"新加坡"},{"n":"马来西亚","v":"马来西亚"},{"n":"印度","v":"印度"},{"n":"英国","v":"英国"},{"n":"法国","v":"法国"},{"n":"加拿大","v":"加拿大"},{"n":"西班牙","v":"西班牙"},{"n":"俄罗斯","v":"俄罗斯"},{"n":"其它","v":"其它"}]},{"key":"year","name":"年代","value":[{"n":"全部","v":""},{"n":"2023","v":"2023"},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"},{"n":"1999","v":"1999"},{"n":"1998","v":"1998"},{"n":"1997","v":"1997"},{"n":"1996","v":"1996"},{"n":"1995","v":"1995"},{"n":"1994","v":"1994"},{"n":"1993","v":"1993"},{"n":"1992","v":"1992"},{"n":"1991","v":"1991"},{"n":"1990","v":"1990"},{"n":"1989","v":"1989"},{"n":"1988","v":"1988"},{"n":"1987","v":"1987"},{"n":"1986","v":"1986"},{"n":"1985","v":"1985"},{"n":"1984","v":"1984"},{"n":"1983","v":"1983"},{"n":"1982","v":"1982"},{"n":"1981","v":"1981"},{"n":"1980","v":"1980"},{"n":"1979","v":"1979"},{"n":"1978","v":"1978"},{"n":"1977","v":"1977"},{"n":"1976","v":"1976"},{"n":"1975","v":"1975"},{"n":"1974","v":"1974"},{"n":"1973","v":"1973"},{"n":"1972","v":"1972"},{"n":"1971","v":"1971"},{"n":"1970","v":"1970"},{"n":"1969","v":"1969"},{"n":"1968","v":"1968"},{"n":"1967","v":"1967"},{"n":"1966","v":"1966"},{"n":"1965","v":"1965"},{"n":"1964","v":"1964"},{"n":"1963","v":"1963"},{"n":"1962","v":"1962"},{"n":"1961","v":"1961"},{"n":"1960","v":"1960"}]},{"key":"by","name":"排序","value":[{"n":"时间","v":"time"},{"n":"人气","v":"hits"},{"n":"评分","v":"score"}]}]}}, + "homeVod": {"list":[{"vod_name":"黑白诀","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v328855398939b42e690e1a9144d0103e4.jpeg","vod_remarks":"更新至20集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51694.html"},{"vod_name":"珠帘玉幕","vod_pic":"http://m.ykimg.com/058400006723580D13EB661331FD30E6?x-oss-process=image/resize,w_312/interlace,1/quality,Q_80","vod_remarks":"更新至14集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51808.html"},{"vod_name":"探晴安","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc00200nhs9g5o1729298236221/260","vod_remarks":"更新至19集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51700.html"},{"vod_name":"龙骨遗冢","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc002005vz2e4v1727706444682/260","vod_remarks":"","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51848.html"},{"vod_name":"好团圆","vod_pic":"https://puui.qpic.cn/vcover_vt_pic/0/mzc00200kot7j0l1695194882212/260","vod_remarks":"更新至23集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51718.html"},{"vod_name":"西北岁月","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc002008c8m73o1730794226058/260","vod_remarks":"更新至02集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51846.html"},{"vod_name":"小巷人家","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/sdp001000atbk1t1730070334/260","vod_remarks":"更新至17集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51739.html"},{"vod_name":"永夜星河","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc002007sqbpce1719196138249/260","vod_remarks":"更新至12集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51809.html"},{"vod_name":"加菲猫家族","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v35d466e73a7214d4ba162a5bfbf8f1fd7.jpeg","vod_remarks":"","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51843.html"},{"vod_name":"黑白有界","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v3ae07cf14507e41df8a1efedadfb08dd5.jpeg","vod_remarks":"","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51842.html"},{"vod_name":"大梦归离","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v3f47b89fb0a1a4e27bf65a0b9c8d8902a.jpeg","vod_remarks":"更新至17集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51717.html"},{"vod_name":"你的谎言也动听","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v3031f14453a114a55846eaf39ccc5824b.jpeg","vod_remarks":"36集全","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51621.html"},{"vod_name":"黑白诀","vod_pic":"","vod_remarks":"更新至20集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51694.html"},{"vod_name":"珠帘玉幕","vod_pic":"","vod_remarks":"更新至14集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51808.html"},{"vod_name":"探晴安","vod_pic":"","vod_remarks":"更新至19集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51700.html"},{"vod_name":"好团圆","vod_pic":"","vod_remarks":"更新至23集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51718.html"},{"vod_name":"西北岁月","vod_pic":"","vod_remarks":"更新至02集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51846.html"},{"vod_name":"小巷人家","vod_pic":"","vod_remarks":"更新至17集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51739.html"},{"vod_name":"永夜星河","vod_pic":"","vod_remarks":"更新至12集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51809.html"},{"vod_name":"大梦归离","vod_pic":"","vod_remarks":"更新至17集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51717.html"},{"vod_name":"你的谎言也动听","vod_pic":"","vod_remarks":"36集全","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51621.html"},{"vod_name":"巾帼枭雄之悬崖","vod_pic":"","vod_remarks":"25集全","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51671.html"},{"vod_name":"双重任务","vod_pic":"","vod_remarks":"25集全","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51696.html"},{"vod_name":"春花焰","vod_pic":"","vod_remarks":"32集全","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51552.html"},{"vod_name":"龙骨遗冢","vod_pic":"","vod_remarks":"","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-51848.html"},{"vod_name":"心动的信号第七季","vod_pic":"","vod_remarks":"更新至20241105","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-50951.html"},{"vod_name":"丹道至尊","vod_pic":"","vod_remarks":"更新至第92集","vod_content":"","vod_id":"https://www.wwgz.cn/vod-detail-id-46811.html"}]}, + "category": {"page":1,"pagecount":999,"limit":20,"total":999,"list":[{"vod_id":"https://www.wwgz.cn/vod-detail-id-51598.html","vod_name":"出租大叔","vod_pic":"https://img.lzzyimg.com/upload/vod/20241017-1/36e6ffbcc023a18fa7cbb28700bf3d27.jpg","vod_remarks":"更新至第16集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-29008.html","vod_name":"爱·回家之开心速递","vod_pic":"https://img.liangzipic.com/upload/vod/20220608-1/3bba6648dbdfe6f442e0492730ec908b.jpg","vod_remarks":"更新至第2415集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51841.html","vod_name":"酒醉罗曼史","vod_pic":"https://img.lzzyimg.com/upload/vod/20241104-1/8a931560d638c4d6cd535a132b6804c5.jpg","vod_remarks":"更新至第02集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51451.html","vod_name":"结婚吧,笨蛋啊!","vod_pic":"https://img.lzzyimg.com/upload/vod/20241008-1/817bad29b19d2688ad58d225380d82bb.jpg","vod_remarks":"更新至第22集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-43316.html","vod_name":"丑闻[韩剧]","vod_pic":"https://img.lzzyimg.com/upload/vod/20240618-1/dd1b04ba4162b5c7a373242d57839ed4.jpg","vod_remarks":"更新至第84集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-39722.html","vod_name":"勇敢无双龙秀晶","vod_pic":"https://img.lzzyimg.com/upload/vod/20240507-1/571404fc7aacea62f65f384576760f4f.jpg","vod_remarks":"更新至第116集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51104.html","vod_name":"我的天才女友第四季","vod_pic":"https://img.lzzyimg.com/upload/vod/20240911-1/8ba2ee8e8cc05c702c5e3ae86b068de3.jpg","vod_remarks":"更新至第09集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51428.html","vod_name":"叔不知","vod_pic":"https://img.lzzyimg.com/upload/vod/20241005-1/b70a1db8bf35c868ac11ca916da00789.jpg","vod_remarks":"更新至第12集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51847.html","vod_name":"极道青年","vod_pic":"https://img.lzzyimg.com/upload/vod/20241105-1/f283c807b2ac3439bb1b570a5370af2d.jpg","vod_remarks":"更新至第01集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51682.html","vod_name":"民王R","vod_pic":"https://img.lzzyimg.com/upload/vod/20241023-1/78589c0f17c127b80ba3ad6ac5151e86.jpg","vod_remarks":"更新至第03集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51564.html","vod_name":"怪物2024","vod_pic":"https://img.lzzyimg.com/upload/vod/20241015-1/c10886eca3fbc906d482f6d6f2ad5d21.jpg","vod_remarks":"更新至第04集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51548.html","vod_name":"OCTO~感情搜查官心野朱梨~第二季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241014-1/c917ecd72ee1f4a6f5df91bbf0e4dc3f.jpg","vod_remarks":"更新至第03集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51464.html","vod_name":"被未来的自己愚弄!?","vod_pic":"https://img.lzzyimg.com/upload/vod/20241008-1/1f0f67b527bf436fc5bf6d4e9ebfade8.jpg","vod_remarks":"更新至第17集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51463.html","vod_name":"解谎侦探少女","vod_pic":"https://img.lzzyimg.com/upload/vod/20241008-1/fd1d9f24693d61e4e169841e49ee6fe0.jpg","vod_remarks":"更新至第05集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51471.html","vod_name":"新版露在火中烧","vod_pic":"https://img.lzzyimg.com/upload/vod/20241009-1/5e878911a9de89db0cfe53b35e6dfd87.jpg","vod_remarks":"更新至第08集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51718.html","vod_name":"好团圆","vod_pic":"https://puui.qpic.cn/vcover_vt_pic/0/mzc00200kot7j0l1695194882212/260","vod_remarks":"更新至23集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51846.html","vod_name":"西北岁月","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc002008c8m73o1730794226058/260","vod_remarks":"更新至02集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51739.html","vod_name":"小巷人家","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/sdp001000atbk1t1730070334/260","vod_remarks":"更新至17集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51809.html","vod_name":"永夜星河","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc002007sqbpce1719196138249/260","vod_remarks":"更新至12集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51808.html","vod_name":"珠帘玉幕","vod_pic":"http://m.ykimg.com/058400006723580D13EB661331FD30E6?x-oss-process=image/resize,w_312/interlace,1/quality,Q_80","vod_remarks":"更新至12集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51694.html","vod_name":"黑白诀","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v328855398939b42e690e1a9144d0103e4.jpeg","vod_remarks":"更新至19集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51700.html","vod_name":"探晴安","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc00200nhs9g5o1729298236221/260","vod_remarks":"更新至18集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51840.html","vod_name":"烈焰国度第三季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241104-1/930ee5cd19e62a4f8af4eabaa6bdfaa8.jpg","vod_remarks":"更新至第01集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51839.html","vod_name":"新殊途同归第二季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241105-1/58cb4e74cf811bb8aff9e8de9d04b244.jpg","vod_remarks":"更新至第01集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51812.html","vod_name":"反恐特警组第八季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241101-1/402a230a3e406737e86b8f4be3b14fe7.jpg","vod_remarks":"更新至第02集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51773.html","vod_name":"某人某地第三季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241028-1/f701af724a809cc9a36741d2bc65b76e.jpg","vod_remarks":"更新至第02集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51713.html","vod_name":"海军罪案调查处第二十二季","vod_pic":"https://img.lzzyimg.com/upload/vod/20241025-1/07e11eb2b3f4c45f8c5bb3bf1f6712c3.jpg","vod_remarks":"已完结"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51450.html","vod_name":"系列大片","vod_pic":"https://img.lzzyimg.com/upload/vod/20241007-1/b5a33b435b0c282c01e1efae8e2a0438.jpg","vod_remarks":"更新至第05集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51299.html","vod_name":"芝加哥急救第十季","vod_pic":"https://img.lzzyimg.com/upload/vod/20240927-1/00556ea2ac6ce26dab4f4c6491228b8e.jpg","vod_remarks":"更新至第05集"},{"vod_id":"https://www.wwgz.cn/vod-detail-id-51298.html","vod_name":"芝加哥警署第十二季","vod_pic":"https://img.lzzyimg.com/upload/vod/20240927-1/75b75ec0987b444f72288118aec14500.jpg","vod_remarks":"更新至第05集"}]}, + "detail": {"list":[ + {"vod_id":"https://www.wwgz.cn/vod-detail-id-51598.html","vod_name":"出租大叔","vod_pic":"https://img.lzzyimg.com/upload/vod/20241017-1/36e6ffbcc023a18fa7cbb28700bf3d27.jpg","type_name":"港台泰","vod_year":"2024","vod_area":"","vod_remarks":"更新日期: 2024-11-06 10:52","vod_actor":"许博文 陆骏光 陈子丰 张达伦 沈贞巧 邱颂伟","vod_director":"陈贤俊","vod_content":"简 介: 生活唔如意﹖噢﹗萬事有大叔﹗看似成熟穩重的西裝精英林木森,為人功利現實又口甜舌滑,愛耍小聰明。在車行工作多年,接觸人多,見盡不同客戶,練就一把好口才,更成為行內 Top Sales。早年離婚的森將女兒林瑪莎的撫養權讓給太太,太太跟女兒移居美國。然而好景不常,疫情過後經濟衰退,車行決定在人工高的他身上開刀,人到中年才失業,愛充大頭鬼的他一時跌下谷低,人生一團糟之際女兒從外國回來,並帶來「出租大叔」的想法﹗森從起初的不願,到渴望靠此生意翻身賺大錢,最後卻發現做「出租大叔」其實是幫助自己理清一團糟的人生,做回最真實的自己⋯⋯ 森索性找來幾個多年好友下海,一同出租,包括:有型才子陸奧、住家男平治、開Cafe的Ben,以及大肚腩的士司機阿田。漸漸地,一班大叔在工作上找回自己的價值,找回生活的熱誠。而林木森在解決客人的問題同時,也解決了自己的問題。其實一切,都是一個學習過程。","vod_play_from":"云播①","vod_play_url":"第01集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-1.html#第02集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-2.html#第03集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-3.html#第04集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-4.html#第05集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-5.html#第06集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-6.html#第07集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-7.html#第08集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-8.html#第09集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-9.html#第10集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-10.html#第11集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-11.html#第12集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-12.html#第13集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-13.html#第14集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-14.html#第15集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-15.html#第16集$https://www.wwgz.cn/vod-play-id-51598-src-1-num-16.html"}, + {"vod_id":"https://www.wwgz.cn/vod-detail-id-51694.html","vod_name":"黑白诀","vod_pic":"https://pic1.58cdn.com.cn/nowater/im/n_v328855398939b42e690e1a9144d0103e4.jpeg","type_name":"国产剧","vod_year":"2024","vod_area":"大陆","vod_remarks":"更新至20集","vod_actor":"范丞丞 邢菲 朱正廷 安悦溪","vod_director":"刘国楠","vod_content":"该剧讲述了在一个黑白颠倒的世界里,正义与邪恶、光明与黑暗之间的较量。主人公在复杂的环境中坚持自己的信念,最终找到真相的故事。","vod_play_from":"云播①","vod_play_url":"第01集$https://www.wwgz.cn/vod-play-id-51694-src-1-num-1.html#第02集$https://www.wwgz.cn/vod-play-id-51694-src-1-num-2.html#第03集$https://www.wwgz.cn/vod-play-id-51694-src-1-num-3.html"}, + {"vod_id":"https://www.wwgz.cn/vod-detail-id-51808.html","vod_name":"珠帘玉幕","vod_pic":"http://m.ykimg.com/058400006723580D13EB661331FD30E6?x-oss-process=image/resize,w_312/interlace,1/quality,Q_80","type_name":"国产剧","vod_year":"2024","vod_area":"大陆","vod_remarks":"更新至14集","vod_actor":"赵露思 刘宇宁 唐晓天 尚新月","vod_director":"惠楷栋","vod_content":"该剧改编自谈天音的小说《昆山玉》,讲述了端王府小郡主端午因为一桩珠宝盗窃案与神机营指挥使燕子京相识,两人从互相怀疑到并肩作战,在朝堂与江湖的风云变幻中携手成长的故事。","vod_play_from":"云播①","vod_play_url":"第01集$https://www.wwgz.cn/vod-play-id-51808-src-1-num-1.html#第02集$https://www.wwgz.cn/vod-play-id-51808-src-1-num-2.html"}, + {"vod_id":"https://www.wwgz.cn/vod-detail-id-51700.html","vod_name":"探晴安","vod_pic":"https://vcover-vt-pic.puui.qpic.cn/vcover_vt_pic/0/mzc00200nhs9g5o1729298236221/260","type_name":"国产剧","vod_year":"2024","vod_area":"大陆","vod_remarks":"更新至19集","vod_actor":"王安宇 徐娇 代斯 牛骏峰","vod_director":"刘海波","vod_content":"该剧讲述了在民国时期,一群年轻人为了探寻真相、维护正义而展开的一系列冒险故事。他们在乱世中坚持理想,用智慧和勇气守护着心中的光明。","vod_play_from":"云播①","vod_play_url":"第01集$https://www.wwgz.cn/vod-play-id-51700-src-1-num-1.html#第02集$https://www.wwgz.cn/vod-play-id-51700-src-1-num-2.html"} + ]}, + "play": {"parse":0,"url":"https://v.cdnlz3.com/20241017/28077_d5cc9644/index.m3u8"}, + "search": {"page":1,"pagecount":10,"limit":20,"total":100,"list":[{"vod_id":"https://www.wwgz.cn/vod-detail-id-46835.html","vod_name":"斗罗大陆2:绝世唐门","vod_pic":"https://pic.lzzypic.com/upload/vod/20230624-1/2734e7689124b78ca9ad7d35132ac6a8.jpg","vod_remarks":"评分:2.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47739.html","vod_name":"斗罗大陆2绝世唐门第1季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221106-1/4674d3d918203d5027ff70100c3804bf.jpg","vod_remarks":"评分:9.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47738.html","vod_name":"斗罗大陆2绝世唐门第2季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221106-1/5d900fa007f4a56e708f29605843f689.jpg","vod_remarks":"评分:1.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47737.html","vod_name":"斗罗大陆2绝世唐门第3季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221106-1/14e8670414aec665ba3af565f44f7491.jpg","vod_remarks":"评分:1.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47728.html","vod_name":"斗罗大陆3龙王传说第1季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221120-1/6a55b5ec170b6b45534bf88fd44c0171.jpg","vod_remarks":"评分:2.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47727.html","vod_name":"斗罗大陆3龙王传说第2季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221120-1/6ea8f6dd434f45f4e002037c37085fe9.jpg","vod_remarks":"评分:3.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47687.html","vod_name":"斗罗大陆2绝世唐门第4季·动态漫","vod_pic":"https://pic.lzzypic.com/upload/vod/20221106-1/a69da5f627a363f54947d754bfb42bbc.jpg","vod_remarks":"评分:4.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47586.html","vod_name":"斗罗大陆外传神界传说 动态漫画","vod_pic":"https://pic.lzzypic.com/upload/vod/20230320-1/ada8b410331b1f2d53cbc42f4f2695f5.jpg","vod_remarks":"评分:5.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47585.html","vod_name":"斗罗大陆外传唐门英雄传 动态漫画","vod_pic":"https://pic.lzzypic.com/upload/vod/20230320-1/5d90a08ccad498f7db26bd1135a2cd27.jpg","vod_remarks":"评分:4.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47518.html","vod_name":"斗罗大陆","vod_pic":"https://pic.lzzypic.com/upload/vod/20220322-1/5383e6de7ffb6a10458baf67b3f99a10.jpg","vod_remarks":"评分:8.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47487.html","vod_name":"动态漫画·斗罗大陆4终极斗罗","vod_pic":"https://pic.lzzypic.com/upload/vod/20220815-1/d03a2595158619a87031e5934d4c1c77.jpg","vod_remarks":"评分:2.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-47319.html","vod_name":"斗罗大陆2绝世唐门第五季","vod_pic":"https://youku.youkuphoto.com/upload/vod/20230521-1/d3c04e7b08ab266d1501df2c4d22d827.jpg","vod_remarks":"评分:3.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-46936.html","vod_name":"动态漫画·斗罗大陆3龙王传说第三季","vod_pic":"https://img.lzzyimg.com/upload/vod/20231117-1/81de84a57bb37e72e96dcc51cf2fee95.jpg","vod_remarks":"评分:1.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-32170.html","vod_name":"瑜伽妹斗罗","vod_pic":"https://img.liangzipic.com/upload/vod/20220916-1/b58e840113dd1c74ea5ec51fb8e34f4f.jpg","vod_remarks":"评分:9.0","vod_content":""},{"vod_id":"https://www.wwgz.cn/vod-detail-id-26541.html","vod_name":"斗罗大陆肖战版","vod_pic":"https://img.liangzipic.com/upload/vod/20220504-1/9679347b4a5f3d64e24aac210b563931.jpg","vod_remarks":"评分:1.0","vod_content":""}]} +} \ No newline at end of file diff --git a/dashboard/public/mock/middlewares.js b/dashboard/public/mock/middlewares.js new file mode 100644 index 0000000..bd11bde --- /dev/null +++ b/dashboard/public/mock/middlewares.js @@ -0,0 +1,12 @@ +module.exports = (request, response, next) => { + if (request.method === 'POST') { + request.method = 'GET' + request.query = request.body + } + + // 处理IE8下的文件上传 + if ((request.headers['content-type'] || '').startsWith('multipart/form-data')) { + response.header('content-type', 'text/html') + } + next() +} diff --git a/dashboard/src/App.vue b/dashboard/src/App.vue index a7612e1..a4644aa 100644 --- a/dashboard/src/App.vue +++ b/dashboard/src/App.vue @@ -1,92 +1,47 @@ diff --git a/dashboard/src/api/README.md b/dashboard/src/api/README.md new file mode 100644 index 0000000..77c9ad7 --- /dev/null +++ b/dashboard/src/api/README.md @@ -0,0 +1,227 @@ +# API 封装使用说明 + +本项目已将所有API调用封装为统一的服务模块,便于维护和使用。 + +## 目录结构 + +``` +src/api/ +├── index.js # 统一入口文件 +├── config.js # API配置和常量 +├── request.js # Axios封装和拦截器 +├── modules/ # 基础API模块 +│ ├── module.js # T4模块数据接口 +│ ├── proxy.js # 模块代理接口 +│ └── parse.js # 解析接口 +├── services/ # 业务服务层 +│ ├── index.js # 服务统一入口 +│ ├── video.js # 视频相关业务逻辑 +│ └── site.js # 站点管理业务逻辑 +├── utils/ # 工具函数 +│ └── index.js # 数据处理和验证工具 +└── types/ # 数据类型定义 + └── index.js # 类型定义和工厂函数 +``` + +## 快速开始 + +### 1. 导入服务 + +```javascript +// 导入所有服务 +import { videoService, siteService } from '@/api/services' + +// 或者单独导入 +import { videoService } from '@/api/services/video' +import { siteService } from '@/api/services/site' +``` + +### 2. 站点管理 + +```javascript +// 获取所有站点 +const sites = siteService.getAllSites() + +// 设置当前站点 +siteService.setCurrentSite('site_key') + +// 获取当前站点 +const currentSite = siteService.getCurrentSite() + +// 添加新站点 +siteService.addSite({ + key: 'new_site', + name: '新站点', + api: 'https://api.example.com', + ext: 'some_extension' +}) +``` + +### 3. 视频数据获取 + +```javascript +// 获取推荐视频 +const homeData = await videoService.getRecommendVideos('site_key', { + extend: 'extension_data' +}) + +// 获取分类视频 +const categoryData = await videoService.getCategoryVideos('site_key', { + t: 'category_id', + pg: 1, + extend: 'extension_data' +}) + +// 搜索视频 +const searchResult = await videoService.searchVideo('site_key', { + wd: '搜索关键词', + pg: 1, + extend: 'extension_data' +}) + +// 获取视频详情 +const videoDetail = await videoService.getVideoDetails('site_key', { + ids: 'video_id', + extend: 'extension_data' +}) + +// 获取播放地址 +const playData = await videoService.getPlayUrl('site_key', { + play: 'play_data', + extend: 'extension_data' +}) +``` + +### 4. 视频解析 + +```javascript +// 解析视频URL +const parseResult = await videoService.parseVideoUrl('jx_key', { + url: 'video_url' +}) +``` + +## 在Vue组件中使用 + +### 基本用法 + +```vue + + + +``` + +### 错误处理 + +所有API调用都包含了错误处理,会返回统一的错误格式: + +```javascript +try { + const result = await videoService.getRecommendVideos('site_key') +} catch (error) { + // error.code - 错误代码 + // error.message - 错误信息 + // error.data - 额外错误数据 + console.error('API调用失败:', error.message) +} +``` + +### 缓存机制 + +视频服务包含5分钟的缓存机制,相同的请求在5分钟内会返回缓存结果,提高性能。 + +## 配置说明 + +### API配置 (config.js) + +```javascript +// 基础配置 +export const BASE_URL = process.env.VUE_APP_API_BASE_URL || '' +export const TIMEOUT = 10000 + +// API路径 +export const API_PATHS = { + MODULE: '/api', // T4模块接口 + PROXY: '/proxy', // 代理接口 + PARSE: '/parse' // 解析接口 +} +``` + +### 请求拦截器 + +请求会自动添加: +- Authorization token(如果存在) +- Cache-Control: no-cache +- 统一的错误处理 + +## 迁移指南 + +### 从旧的req方式迁移 + +**旧方式:** +```javascript +import req from '@/utils/req' + +// 获取数据 +const response = await req.get('/home') +const data = response.data +``` + +**新方式:** +```javascript +import { videoService, siteService } from '@/api/services' + +// 获取数据 +const currentSite = siteService.getCurrentSite() +const data = await videoService.getRecommendVideos(currentSite.key, { + extend: currentSite.ext +}) +``` + +### 主要变化 + +1. **统一的服务接口**:不再直接调用HTTP方法,而是调用语义化的业务方法 +2. **自动错误处理**:统一的错误格式和处理机制 +3. **数据格式化**:返回的数据已经过格式化处理 +4. **缓存支持**:自动缓存机制提高性能 +5. **类型安全**:完整的类型定义和验证 + +## 注意事项 + +1. 所有API调用都需要先设置当前站点 +2. 确保传入正确的extend参数 +3. 处理好异步操作的错误情况 +4. 合理使用缓存机制,避免频繁请求 \ No newline at end of file diff --git a/dashboard/src/api/config.js b/dashboard/src/api/config.js new file mode 100644 index 0000000..e6e1f37 --- /dev/null +++ b/dashboard/src/api/config.js @@ -0,0 +1,97 @@ +/** + * API配置文件 + * 定义接口相关的配置信息和常量 + */ + +/** + * 获取API超时时间(毫秒) + * 从设置中读取,如果没有设置则使用默认值30秒 + */ +export const getApiTimeout = () => { + try { + const addressSettings = JSON.parse(localStorage.getItem('addressSettings') || '{}') + const timeoutSeconds = addressSettings.apiTimeout || 30 + return timeoutSeconds * 1000 // 转换为毫秒 + } catch (error) { + console.warn('Failed to get API timeout from settings, using default:', error) + return 30000 // 默认30秒 + } +} + +/** + * 获取Action接口超时时间(毫秒) + * Action接口通常需要更长的超时时间,默认100秒 + */ +export const getActionTimeout = () => { + try { + const addressSettings = JSON.parse(localStorage.getItem('addressSettings') || '{}') + const apiTimeoutSeconds = addressSettings.apiTimeout || 30 + // Action接口超时时间为普通接口的3倍,但最少100秒 + const actionTimeoutSeconds = Math.max(apiTimeoutSeconds * 3, 100) + return actionTimeoutSeconds * 1000 // 转换为毫秒 + } catch (error) { + console.warn('Failed to get Action timeout from settings, using default:', error) + return 100000 // 默认100秒 + } +} + +// 基础配置 +export const API_CONFIG = { + // 基础URL配置 + BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5707', + + // 超时配置(动态获取) + get TIMEOUT() { + return getApiTimeout() + }, + + // 请求头配置 + HEADERS: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } +} + +// 接口路径常量 +export const API_PATHS = { + // 模块数据接口 + MODULE: '/api', + + // 代理接口 + PROXY: '/proxy', + + // 解析接口 + PARSE: '/parse' +} + +// 模块功能类型 +export const MODULE_ACTIONS = { + PLAY: 'play', // 播放接口 + CATEGORY: 'category', // 分类接口 + DETAIL: 'detail', // 详情接口 + ACTION: 'action', // 动作接口 + SEARCH: 'search', // 搜索接口 + REFRESH: 'refresh', // 刷新接口 + HOME: 'home' // 首页接口(默认) +} + +// 响应状态码 +export const RESPONSE_CODES = { + SUCCESS: 200, + NOT_FOUND: 404, + SERVER_ERROR: 500 +} + +// 默认分页配置 +export const PAGINATION = { + DEFAULT_PAGE: 1, + DEFAULT_PAGE_SIZE: 20 +} + +export default { + API_CONFIG, + API_PATHS, + MODULE_ACTIONS, + RESPONSE_CODES, + PAGINATION +} \ No newline at end of file diff --git a/dashboard/src/api/index.js b/dashboard/src/api/index.js new file mode 100644 index 0000000..9a803de --- /dev/null +++ b/dashboard/src/api/index.js @@ -0,0 +1,35 @@ +/** + * API模块统一入口 + * 提供所有业务接口的统一导出 + */ + +// 基础配置和工具 +export { default as request } from './request' +export { default as config } from './config' + +// 业务服务 +export { default as siteService } from './services/site' +export { default as videoService } from './services/video' +export { default as configService } from './services/config' + +// 业务接口模块 +export { default as moduleApi } from './modules/module' +export { default as proxyApi } from './modules/proxy' +export { default as parseApi } from './modules/parse' + +// 便捷导出常用接口 +export { + getHomeData, + getCategoryData, + getVideoDetail, + searchVideos, + refreshModule +} from './modules/module' + +export { + proxyRequest +} from './modules/proxy' + +export { + parseVideo +} from './modules/parse' \ No newline at end of file diff --git a/dashboard/src/api/modules/module.js b/dashboard/src/api/modules/module.js new file mode 100644 index 0000000..897a8ef --- /dev/null +++ b/dashboard/src/api/modules/module.js @@ -0,0 +1,528 @@ +/** + * 模块数据接口 (T4接口) + * 封装 /api/:module 相关的所有接口调用 + */ + +import { get, post } from '../request' +import { API_PATHS, MODULE_ACTIONS, PAGINATION, API_CONFIG } from '../config' +import { processExtendParam } from '@/utils/apiUtils' +import axios from 'axios' + +/** + * 解析headers字段,支持对象和JSON字符串格式 + * @param {Object|string} headers - headers字段 + * @returns {Object} 解析后的headers对象 + */ +const parseHeaders = (headers) => { + if (!headers) { + console.log('🔍 [Headers解析] 输入为空,返回空对象') + return {} + } + + console.log('🔍 [Headers解析] 输入数据:', headers, '类型:', typeof headers) + + // 如果已经是对象,直接返回 + if (typeof headers === 'object' && headers !== null) { + console.log('🔍 [Headers解析] 已是对象,直接返回:', headers) + return headers + } + + // 如果是字符串,尝试解析为JSON + if (typeof headers === 'string') { + try { + const parsed = JSON.parse(headers) + // 确保解析结果是对象 + const result = typeof parsed === 'object' && parsed !== null ? parsed : {} + console.log('🔍 [Headers解析] JSON字符串解析成功:', result) + return result + } catch (error) { + console.warn('🔍 [Headers解析] JSON字符串解析失败:', error, '原始数据:', headers) + return {} + } + } + + console.log('🔍 [Headers解析] 未知类型,返回空对象') + return {} +} + + + +/** + * 构建模块接口URL + * @param {string} module - 模块名称 + * @returns {string} 完整的接口URL + */ +const buildModuleUrl = (module) => { + // 对模块名称进行URL编码以支持中文字符 + const encodedModule = encodeURIComponent(module) + return `${API_PATHS.MODULE}/${encodedModule}` +} + +/** + * 直接调用站点API + * @param {string} apiUrl - 站点API地址 + * @param {object} params - 请求参数 + * @returns {Promise} API响应 + */ +const directApiCall = async (apiUrl, params = {}) => { + try { + const response = await axios.get(apiUrl, { + params, + timeout: API_CONFIG.TIMEOUT, + headers: { + 'Accept': 'application/json' + } + }) + + return response.data + } catch (error) { + console.error('直接API调用失败:', error) + throw error + } +} + +/** + * 获取首页数据(默认接口) + * @param {string} module - 模块名称 + * @param {object} options - 选项参数 + * @param {number} options.filter - 过滤条件(1表示启用,默认启用) + * @param {string|object} options.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} options.apiUrl - 站点API地址(可选,如果提供则直接使用) + * @returns {Promise} 首页数据 + */ +export const getHomeData = async (module, options = {}) => { + const { filter = 1, extend, apiUrl } = options + const params = { filter } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + params.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, params) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), params) +} + +/** + * 获取分类数据 + * @param {string} module - 模块名称 + * @param {object} params - 分类参数 + * @param {string} params.ac - 固定值 "category" + * @param {string} params.t - 分类ID + * @param {number} params.pg - 页码(从1开始) + * @param {string} params.ext - base64编码的筛选条件JSON字符串 + * @param {string|object} params.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} params.apiUrl - 可选的直接API地址 + * @returns {Promise} 分类数据 + */ +export const getCategoryData = async (module, params) => { + const { + t: typeId, + pg = PAGINATION.DEFAULT_PAGE, + ext, + extend, + apiUrl + } = params + + const requestParams = { + ac: MODULE_ACTIONS.CATEGORY, + t: typeId, + pg + } + + if (ext) { + requestParams.ext = ext + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, requestParams) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), requestParams) +} + +/** + * 获取视频详情 + * @param {string} module - 模块名称 + * @param {object} params - 详情参数 + * @param {string} params.ids - 视频ID + * @param {string|object} params.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} params.apiUrl - 可选的直接API地址 + * @returns {Promise} 视频详情数据 + */ +export const getVideoDetail = async (module, params) => { + const { ids, extend, apiUrl } = params + + const requestParams = { + ac: MODULE_ACTIONS.DETAIL, + ids + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, requestParams) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), requestParams) +} + +/** + * 播放接口 + * @param {string} module - 模块名称 + * @param {object} params - 播放参数 + * @param {string} params.play - 播放地址或ID + * @param {string} params.flag - 源标识(线路名称) + * @param {string|object} params.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} params.apiUrl - 可选的直接API地址 + * @returns {Promise} 播放数据 + */ +export const getPlayData = async (module, params) => { + const { play, flag, extend, apiUrl } = params + + const requestParams = { + ac: MODULE_ACTIONS.PLAY, + play + } + + // 添加flag参数支持 + if (flag) { + requestParams.flag = flag + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, requestParams) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), requestParams) +} + +/** + * 播放解析接口 - 专门用于选集播放解析 + * @param {string} module - 模块名称 + * @param {object} params - 播放参数 + * @param {string} params.play - 播放地址或ID(选集链接) + * @param {string} params.flag - 源标识(线路名称) + * @param {string|object} params.extend - 接口数据扩展参数 + * @param {string} params.apiUrl - 可选的直接API地址 + * @returns {Promise} 播放解析结果 + */ +export const parsePlayUrl = async (module, params) => { + try { + console.log('T4播放解析请求:', { module, params }) + + const playData = await getPlayData(module, params) + console.log('T4播放解析响应:', playData) + + // 调试:显示原始headers数据 + const rawHeaders = playData?.headers || playData?.header + if (rawHeaders) { + console.log('T4接口返回的原始headers:', rawHeaders, '类型:', typeof rawHeaders) + } + + // 处理解析结果 + const result = { + success: true, + data: playData, + // 解析播放类型 + playType: 'direct', // 默认直链 + url: '', + headers: {}, // 添加headers字段 + danmaku: '', // 添加弹幕字段 + needParse: false, + needSniff: false, + message: '' + } + + // 检查返回数据格式 + if (playData && typeof playData === 'object') { + // 检查parse和jx字段,jx:1优先级高于parse:1 + if (playData.jx === 1) { + // 需要解析 - 优先级最高 + result.playType = 'parse' + result.url = playData.url || playData.play_url || '' + result.headers = parseHeaders(playData.headers || playData.header) + result.danmaku = playData.danmaku || '' // 处理弹幕字段 + result.needParse = true + result.qualities = [] + result.hasMultipleQualities = false + result.message = '需要解析才能播放,尽情期待' + } else if (playData.parse === 0) { + // 直链播放 + result.playType = 'direct' + + // 处理URL字段 - 支持数组格式的多画质 + const urlData = playData.url || playData.play_url || '' + if (Array.isArray(urlData)) { + // URL是数组格式,包含多画质信息 + console.log('检测到多画质URL数组:', urlData) + + // 解析画质数组:奇数索引是画质名称,偶数索引是对应链接 + const qualities = [] + for (let i = 0; i < urlData.length; i += 2) { + if (i + 1 < urlData.length) { + const qualityName = urlData[i]?.toString().trim() + const qualityUrl = urlData[i + 1]?.toString().trim() + if (qualityName && qualityUrl) { + qualities.push({ + name: qualityName, + url: qualityUrl + }) + } + } + } + + console.log('解析出的画质列表:', qualities) + + // 设置多画质数据 + result.qualities = qualities + result.hasMultipleQualities = qualities.length > 1 + + // 默认使用第一个画质 + if (qualities.length > 0) { + result.url = qualities[0].url + result.currentQuality = qualities[0].name + result.message = `多画质播放 (当前: ${qualities[0].name})` + } else { + result.url = '' + result.message = '多画质数据解析失败' + } + } else { + // URL是字符串格式,单一画质 + result.url = urlData + result.qualities = [] + result.hasMultipleQualities = false + result.currentQuality = '默认' + result.message = '直链播放' + } + + result.headers = parseHeaders(playData.headers || playData.header) + result.danmaku = playData.danmaku || '' // 处理弹幕字段 + result.needParse = false + result.needSniff = false + } else if (playData.parse === 1) { + // 需要嗅探 + result.playType = 'sniff' + result.url = playData.url || playData.play_url || '' + result.headers = parseHeaders(playData.headers || playData.header) + result.danmaku = playData.danmaku || '' // 处理弹幕字段 + result.needSniff = true + result.qualities = [] + result.hasMultipleQualities = false + result.message = '需要嗅探才能播放,尽情期待' + } else { + // 默认处理为直链 + result.url = playData.url || playData.play_url || playData + result.headers = parseHeaders(playData.headers || playData.header) + result.danmaku = playData.danmaku || '' // 处理弹幕字段 + result.qualities = [] + result.hasMultipleQualities = false + result.message = '直链播放' + } + } else if (typeof playData === 'string') { + // 如果返回的是字符串,直接作为播放地址 + result.url = playData + result.headers = {} + result.danmaku = '' // 字符串类型无弹幕数据 + result.qualities = [] + result.hasMultipleQualities = false + result.message = '直链播放' + } + + return result + } catch (error) { + console.error('T4播放解析失败:', error) + return { + success: false, + error: error.message || '播放解析失败', + playType: 'error', + url: '', + headers: {}, + needParse: false, + needSniff: false, + message: '播放解析失败: ' + (error.message || '未知错误') + } + } +} + +/** + * 搜索接口 + * @param {string} module - 模块名称 + * @param {object} params - 搜索参数 + * @param {string} params.wd - 搜索关键词 + * @param {number} params.pg - 页码(从1开始) + * @param {string|object} params.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} params.apiUrl - 可选的直接API地址 + * @returns {Promise} 搜索结果 + */ +export const searchVideos = async (module, params) => { + const { + wd: keyword, + pg = PAGINATION.DEFAULT_PAGE, + extend, + apiUrl + } = params + + const requestParams = { + wd: keyword, + pg + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, requestParams) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), requestParams) +} + +/** + * 动作接口(POST请求) + * @param {string} module - 模块名称 + * @param {object} data - 动作数据 + * @param {string} data.action - 动作类型 + * @param {string|object} data.extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} data.apiUrl - 可选的直接API地址 + * @returns {Promise} 动作执行结果 + */ +export const executeAction = async (module, data) => { + const { action, extend, apiUrl, ...otherData } = data + + const requestData = { + ac: MODULE_ACTIONS.ACTION, + action, + ...otherData + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestData.extend = processedExtend + } + + console.log('executeAction调用参数:', { + module, + data, + requestData, + apiUrl + }) + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + console.log('直接调用API:', apiUrl, requestData) + + // 如果是测试用的JSON文件,使用GET请求 + if (apiUrl.endsWith('.json')) { + const response = await axios.get(apiUrl, { + timeout: API_CONFIG.TIMEOUT, + headers: { + 'Accept': 'application/json' + } + }) + console.log('API响应 (GET):', response.data) + return response.data + } else { + // 否则使用POST请求 + const response = await axios.post(apiUrl, requestData, { + timeout: API_CONFIG.TIMEOUT, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json;charset=UTF-8', + } + }) + console.log('API响应 (POST):', response.data) + return response.data + } + } + + // 否则使用原来的代理方式 + console.log('使用代理方式调用:', buildModuleUrl(module), requestData) + const result = await post(buildModuleUrl(module), requestData) + console.log('代理响应:', result) + return result +} + +/** + * 刷新模块数据 + * @param {string} module - 模块名称 + * @param {string|object} extend - 接口数据扩展参数(对象类型会自动转换为JSON字符串) + * @param {string} apiUrl - 可选的直接API地址 + * @returns {Promise} 刷新结果 + */ +export const refreshModule = async (module, extend, apiUrl) => { + const params = { + refresh: '1' + } + + const processedExtend = processExtendParam(extend) + if (processedExtend) { + params.extend = processedExtend + } + + // 如果提供了apiUrl,直接使用站点的API地址 + if (apiUrl) { + return directApiCall(apiUrl, params) + } + + // 否则使用原来的代理方式 + return get(buildModuleUrl(module), params) +} + +/** + * 通用模块接口调用 + * @param {string} module - 模块名称 + * @param {object} params - 请求参数 + * @param {string} method - 请求方法 ('GET' | 'POST') + * @returns {Promise} 接口响应 + */ +export const callModuleApi = async (module, params = {}, method = 'GET') => { + const url = buildModuleUrl(module) + + if (method.toUpperCase() === 'POST') { + return post(url, params) + } else { + return get(url, params) + } +} + +// 默认导出所有接口 +export default { + getHomeData, + getCategoryData, + getVideoDetail, + getPlayData, + parsePlayUrl, + searchVideos, + executeAction, + refreshModule, + callModuleApi +} \ No newline at end of file diff --git a/dashboard/src/api/modules/parse.js b/dashboard/src/api/modules/parse.js new file mode 100644 index 0000000..25a4dd9 --- /dev/null +++ b/dashboard/src/api/modules/parse.js @@ -0,0 +1,167 @@ +/** + * 解析接口 + * 封装 /parse/:jx 相关的视频解析接口 + */ + +import { get, post } from '../request' +import { API_PATHS } from '../config' + +/** + * 构建解析接口URL + * @param {string} jx - 解析器名称 + * @returns {string} 完整的解析URL + */ +const buildParseUrl = (jx) => { + return `${API_PATHS.PARSE}/${jx}` +} + +/** + * 解析视频地址 + * @param {string} jx - 解析器名称 + * @param {object} params - 解析参数 + * @param {string} params.url - 需要解析的视频URL + * @param {string} params.flag - 解析标识(可选) + * @param {object} params.headers - 自定义请求头(可选) + * @returns {Promise} 解析结果 + */ +export const parseVideo = async (jx, params) => { + const { url, flag, headers, ...otherParams } = params + + if (!url) { + throw new Error('视频URL不能为空') + } + + const requestParams = { + url, + ...otherParams + } + + if (flag) { + requestParams.flag = flag + } + + const requestOptions = {} + if (headers) { + requestOptions.headers = { + ...requestOptions.headers, + ...headers + } + } + + return get(buildParseUrl(jx), requestParams, requestOptions) +} + +/** + * POST方式解析视频(用于复杂参数) + * @param {string} jx - 解析器名称 + * @param {object} data - 解析数据 + * @param {string} data.url - 需要解析的视频URL + * @param {string} data.flag - 解析标识(可选) + * @param {object} data.headers - 自定义请求头(可选) + * @returns {Promise} 解析结果 + */ +export const parseVideoPost = async (jx, data) => { + const { url, headers, ...requestData } = data + + if (!url) { + throw new Error('视频URL不能为空') + } + + const requestOptions = {} + if (headers) { + requestOptions.headers = { + ...requestOptions.headers, + ...headers + } + } + + return post(buildParseUrl(jx), requestData, requestOptions) +} + +/** + * 批量解析视频 + * @param {string} jx - 解析器名称 + * @param {Array} urls - 视频URL数组 + * @param {object} options - 解析选项 + * @param {string} options.flag - 解析标识(可选) + * @param {object} options.headers - 自定义请求头(可选) + * @returns {Promise} 批量解析结果 + */ +export const parseVideoBatch = async (jx, urls, options = {}) => { + if (!Array.isArray(urls) || urls.length === 0) { + throw new Error('视频URL数组不能为空') + } + + const { flag, headers } = options + + const requestData = { + urls, + batch: true + } + + if (flag) { + requestData.flag = flag + } + + const requestOptions = {} + if (headers) { + requestOptions.headers = { + ...requestOptions.headers, + ...headers + } + } + + return post(buildParseUrl(jx), requestData, requestOptions) +} + +/** + * 获取解析器信息 + * @param {string} jx - 解析器名称 + * @returns {Promise} 解析器信息 + */ +export const getParserInfo = async (jx) => { + return get(buildParseUrl(jx), { info: true }) +} + +/** + * 测试解析器可用性 + * @param {string} jx - 解析器名称 + * @param {string} testUrl - 测试URL(可选) + * @returns {Promise} 测试结果 + */ +export const testParser = async (jx, testUrl) => { + const params = { test: true } + + if (testUrl) { + params.url = testUrl + } + + return get(buildParseUrl(jx), params) +} + +/** + * 通用解析接口调用 + * @param {string} jx - 解析器名称 + * @param {object} params - 请求参数 + * @param {string} method - 请求方法 ('GET' | 'POST') + * @returns {Promise} 解析响应 + */ +export const callParseApi = async (jx, params = {}, method = 'GET') => { + const url = buildParseUrl(jx) + + if (method.toUpperCase() === 'POST') { + return post(url, params) + } else { + return get(url, params) + } +} + +// 默认导出所有解析接口 +export default { + parseVideo, + parseVideoPost, + parseVideoBatch, + getParserInfo, + testParser, + callParseApi +} \ No newline at end of file diff --git a/dashboard/src/api/modules/proxy.js b/dashboard/src/api/modules/proxy.js new file mode 100644 index 0000000..f8cd7ee --- /dev/null +++ b/dashboard/src/api/modules/proxy.js @@ -0,0 +1,136 @@ +/** + * 模块代理接口 + * 封装 /proxy/:module/* 相关的代理转发接口 + */ + +import { get, post, put, del } from '../request' +import { API_PATHS } from '../config' + +/** + * 构建代理接口URL + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @returns {string} 完整的代理URL + */ +const buildProxyUrl = (module, path = '') => { + const basePath = `${API_PATHS.PROXY}/${module}` + return path ? `${basePath}/${path}` : basePath +} + +/** + * 代理GET请求 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} params - 查询参数 + * @returns {Promise} 代理响应 + */ +export const proxyGet = async (module, path = '', params = {}) => { + const url = buildProxyUrl(module, path) + return get(url, params) +} + +/** + * 代理POST请求 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} data - 请求数据 + * @returns {Promise} 代理响应 + */ +export const proxyPost = async (module, path = '', data = {}) => { + const url = buildProxyUrl(module, path) + return post(url, data) +} + +/** + * 代理PUT请求 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} data - 请求数据 + * @returns {Promise} 代理响应 + */ +export const proxyPut = async (module, path = '', data = {}) => { + const url = buildProxyUrl(module, path) + return put(url, data) +} + +/** + * 代理DELETE请求 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} params - 查询参数 + * @returns {Promise} 代理响应 + */ +export const proxyDelete = async (module, path = '', params = {}) => { + const url = buildProxyUrl(module, path) + return del(url, params) +} + +/** + * 通用代理请求 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} options - 请求选项 + * @param {string} options.method - 请求方法 + * @param {object} options.params - 查询参数(GET请求) + * @param {object} options.data - 请求数据(POST/PUT请求) + * @returns {Promise} 代理响应 + */ +export const proxyRequest = async (module, path = '', options = {}) => { + const { method = 'GET', params, data } = options + + switch (method.toUpperCase()) { + case 'GET': + return proxyGet(module, path, params) + case 'POST': + return proxyPost(module, path, data) + case 'PUT': + return proxyPut(module, path, data) + case 'DELETE': + return proxyDelete(module, path, params) + default: + throw new Error(`不支持的请求方法: ${method}`) + } +} + +/** + * 代理文件上传 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {FormData} formData - 文件数据 + * @returns {Promise} 上传响应 + */ +export const proxyUpload = async (module, path = '', formData) => { + const url = buildProxyUrl(module, path) + + return post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +/** + * 代理文件下载 + * @param {string} module - 模块名称 + * @param {string} path - 代理路径 + * @param {object} params - 查询参数 + * @returns {Promise} 下载响应 + */ +export const proxyDownload = async (module, path = '', params = {}) => { + const url = buildProxyUrl(module, path) + + return get(url, params, { + responseType: 'blob' + }) +} + +// 默认导出所有代理接口 +export default { + proxyGet, + proxyPost, + proxyPut, + proxyDelete, + proxyRequest, + proxyUpload, + proxyDownload +} \ No newline at end of file diff --git a/dashboard/src/api/parser.js b/dashboard/src/api/parser.js new file mode 100644 index 0000000..5ddac8a --- /dev/null +++ b/dashboard/src/api/parser.js @@ -0,0 +1,348 @@ +import { useParserStore } from '@/stores/parser' + +/** + * 解析服务API + * 提供视频解析功能,支持播放器通过 jx:1 格式调用 + */ +export class ParserService { + constructor() { + this.parserStore = useParserStore() + } + + /** + * 解析视频地址 + * @param {string} videoUrl - 原始视频地址 + * @param {string} flag - 播放器标识 (可选) + * @param {number} parserIndex - 指定解析器索引 (可选) + * @returns {Promise} 解析结果 + */ + async parseVideo(videoUrl, flag = null, parserIndex = null) { + try { + // 获取可用的解析器 + let availableParsers = this.parserStore.enabledParsers + + // 如果指定了播放器标识,过滤支持该标识的解析器 + if (flag) { + availableParsers = availableParsers.filter(parser => + parser.ext?.flag?.includes(flag) + ) + } + + // 如果指定了解析器索引,使用指定的解析器 + if (parserIndex !== null && availableParsers[parserIndex]) { + availableParsers = [availableParsers[parserIndex]] + } + + if (availableParsers.length === 0) { + throw new Error('没有可用的解析器') + } + + // 尝试使用解析器解析视频 + for (const parser of availableParsers) { + try { + const result = await this.parseWithParser(parser, videoUrl) + if (result.success) { + return { + success: true, + data: result.data, + parser: { + name: parser.name, + id: parser.id + }, + message: '解析成功' + } + } + } catch (err) { + console.warn(`解析器 ${parser.name} 解析失败:`, err.message) + continue + } + } + + throw new Error('所有解析器都解析失败') + } catch (err) { + return { + success: false, + error: err.message, + message: `解析失败: ${err.message}` + } + } + } + + /** + * 使用指定解析器解析视频 + * @param {Object} parser - 解析器配置 + * @param {string} videoUrl - 视频地址 + * @returns {Promise} 解析结果 + */ + async parseWithParser(parser, videoUrl) { + try { + // 构建解析请求URL + const parseUrl = parser.url.replace(/\{url\}/g, encodeURIComponent(videoUrl)) + + // 设置请求头 + const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + ...parser.header + } + + const response = await fetch(parseUrl, { + method: 'GET', + headers, + timeout: 15000 // 15秒超时 + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const contentType = response.headers.get('content-type') + let result + + // 根据解析器类型处理响应 + if (parser.type === '1' || contentType?.includes('application/json')) { + // JSON 类型解析器 + result = await response.json() + return this.parseJsonResponse(result) + } else { + // 嗅探类型解析器 + result = await response.text() + return this.parseTextResponse(result) + } + } catch (err) { + throw new Error(`解析器请求失败: ${err.message}`) + } + } + + /** + * 解析JSON响应 + * @param {Object} data - JSON数据 + * @returns {Object} 解析结果 + */ + parseJsonResponse(data) { + try { + // 常见的JSON响应格式 + let videoUrl = null + let videoUrls = [] + + // 尝试不同的字段名 + const urlFields = ['url', 'video', 'src', 'link', 'address', 'path'] + const urlsFields = ['urls', 'videos', 'sources', 'links'] + + // 查找单个视频地址 + for (const field of urlFields) { + if (data[field] && typeof data[field] === 'string') { + videoUrl = data[field] + break + } + } + + // 查找多个视频地址 + for (const field of urlsFields) { + if (data[field] && Array.isArray(data[field])) { + videoUrls = data[field] + break + } + } + + // 如果没有找到单个地址,尝试从数组中取第一个 + if (!videoUrl && videoUrls.length > 0) { + videoUrl = typeof videoUrls[0] === 'string' ? videoUrls[0] : videoUrls[0].url + } + + if (!videoUrl) { + throw new Error('未找到视频地址') + } + + // 验证是否为有效的视频URL + if (!this.isValidVideoUrl(videoUrl)) { + throw new Error('解析到的不是有效的视频地址') + } + + return { + success: true, + data: { + url: videoUrl, + urls: videoUrls, + type: 'json', + raw: data + } + } + } catch (err) { + throw new Error(`JSON解析失败: ${err.message}`) + } + } + + /** + * 解析文本响应 + * @param {string} text - 文本数据 + * @returns {Object} 解析结果 + */ + parseTextResponse(text) { + try { + // 尝试提取视频URL + const videoUrls = this.extractVideoUrls(text) + + if (videoUrls.length === 0) { + throw new Error('未找到视频地址') + } + + return { + success: true, + data: { + url: videoUrls[0], + urls: videoUrls, + type: 'text', + raw: text + } + } + } catch (err) { + throw new Error(`文本解析失败: ${err.message}`) + } + } + + /** + * 从文本中提取视频URL + * @param {string} text - 文本内容 + * @returns {Array} 视频URL数组 + */ + extractVideoUrls(text) { + const videoUrls = [] + + // 常见的视频URL正则表达式 + const patterns = [ + // M3U8 格式 + /https?:\/\/[^\s"'<>]+\.m3u8[^\s"'<>]*/gi, + // MP4 格式 + /https?:\/\/[^\s"'<>]+\.mp4[^\s"'<>]*/gi, + // FLV 格式 + /https?:\/\/[^\s"'<>]+\.flv[^\s"'<>]*/gi, + // 其他视频格式 + /https?:\/\/[^\s"'<>]+\.(avi|mkv|wmv|mov|webm)[^\s"'<>]*/gi, + // 通用视频流URL + /https?:\/\/[^\s"'<>]*(?:video|stream|play)[^\s"'<>]*/gi + ] + + for (const pattern of patterns) { + const matches = text.match(pattern) + if (matches) { + videoUrls.push(...matches) + } + } + + // 去重并过滤有效URL + return [...new Set(videoUrls)].filter(url => this.isValidVideoUrl(url)) + } + + /** + * 验证是否为有效的视频URL + * @param {string} url - URL地址 + * @returns {boolean} 是否有效 + */ + isValidVideoUrl(url) { + try { + const urlObj = new URL(url) + + // 检查协议 + if (!['http:', 'https:'].includes(urlObj.protocol)) { + return false + } + + // 检查是否包含视频相关关键词或扩展名 + const videoKeywords = ['video', 'stream', 'play', 'm3u8', 'mp4', 'flv', 'avi', 'mkv'] + const urlLower = url.toLowerCase() + + return videoKeywords.some(keyword => urlLower.includes(keyword)) + } catch { + return false + } + } + + /** + * 获取解析器列表 + * @returns {Array} 解析器列表 + */ + getParsers() { + return this.parserStore.parsers.map(parser => ({ + id: parser.id, + name: parser.name, + enabled: parser.enabled, + type: parser.type, + flags: parser.ext?.flag || [] + })) + } + + /** + * 获取启用的解析器列表 + * @returns {Array} 启用的解析器列表 + */ + getEnabledParsers() { + return this.parserStore.enabledParsers.map(parser => ({ + id: parser.id, + name: parser.name, + type: parser.type, + flags: parser.ext?.flag || [] + })) + } + + /** + * 根据播放器标识获取支持的解析器 + * @param {string} flag - 播放器标识 + * @returns {Array} 支持的解析器列表 + */ + getParsersByFlag(flag) { + return this.parserStore.enabledParsers + .filter(parser => parser.ext?.flag?.includes(flag)) + .map(parser => ({ + id: parser.id, + name: parser.name, + type: parser.type + })) + } +} + +// 创建单例实例 +export const parserService = new ParserService() + +/** + * 解析视频地址的便捷函数 + * @param {string} videoUrl - 视频地址 + * @param {Object} options - 选项 + * @returns {Promise} 解析结果 + */ +export async function parseVideo(videoUrl, options = {}) { + const { flag, parserIndex } = options + return await parserService.parseVideo(videoUrl, flag, parserIndex) +} + +/** + * 处理 jx:1 格式的播放地址 + * @param {string} playUrl - 播放地址 (格式: jx:1$视频地址) + * @returns {Promise} 解析结果 + */ +export async function handleJxUrl(playUrl) { + try { + // 解析 jx:1 格式 + const match = playUrl.match(/^jx:(\d+)\$(.+)$/) + if (!match) { + throw new Error('无效的jx格式地址') + } + + const parserIndex = parseInt(match[1]) - 1 // 转换为0基索引 + const videoUrl = match[2] + + return await parserService.parseVideo(videoUrl, null, parserIndex) + } catch (err) { + return { + success: false, + error: err.message, + message: `jx地址解析失败: ${err.message}` + } + } +} + +export default { + ParserService, + parserService, + parseVideo, + handleJxUrl +} \ No newline at end of file diff --git a/dashboard/src/api/request.js b/dashboard/src/api/request.js new file mode 100644 index 0000000..07e6440 --- /dev/null +++ b/dashboard/src/api/request.js @@ -0,0 +1,157 @@ +/** + * 基础请求工具 + * 封装axios,提供统一的请求处理和错误处理 + */ + +import axios from 'axios' +import { API_CONFIG, RESPONSE_CODES } from './config' + +// 创建axios实例 +const request = axios.create({ + baseURL: API_CONFIG.BASE_URL, + timeout: API_CONFIG.TIMEOUT, + headers: API_CONFIG.HEADERS +}) + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + // 添加认证token(如果存在) + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + + // 添加时间戳防止缓存 + if (config.method === 'get') { + config.params = { + ...config.params, + _t: Date.now() + } + } + + console.log('API Request:', config.method?.toUpperCase(), config.url, config.params || config.data) + return config + }, + (error) => { + console.error('Request Error:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + (response) => { + const { data, status } = response + + console.log('API Response:', response.config.url, data) + + // 检查HTTP状态码 + if (status !== 200) { + throw new Error(`HTTP Error: ${status}`) + } + + // 检查业务状态码 + if (data.code && data.code !== RESPONSE_CODES.SUCCESS) { + throw new Error(data.msg || `Business Error: ${data.code}`) + } + + return data + }, + (error) => { + console.error('Response Error:', error) + + // 处理网络错误 + if (!error.response) { + throw new Error('网络连接失败,请检查网络设置') + } + + // 处理HTTP错误状态码 + const { status } = error.response + switch (status) { + case 404: + throw new Error('请求的资源不存在') + case 500: + throw new Error('服务器内部错误') + case 401: + throw new Error('未授权访问') + case 403: + throw new Error('访问被禁止') + default: + throw new Error(`请求失败: ${status}`) + } + } +) + +/** + * 通用请求方法 + * @param {string} url - 请求URL + * @param {object} options - 请求选项 + * @returns {Promise} 请求结果 + */ +export const apiRequest = async (url, options = {}) => { + try { + const response = await request({ + url, + ...options + }) + return response + } catch (error) { + console.error('API Request Failed:', error.message) + throw error + } +} + +/** + * GET请求 + * @param {string} url - 请求URL + * @param {object} params - 查询参数 + * @returns {Promise} 请求结果 + */ +export const get = (url, params = {}) => { + return apiRequest(url, { + method: 'GET', + params + }) +} + +/** + * POST请求 + * @param {string} url - 请求URL + * @param {object} data - 请求数据 + * @returns {Promise} 请求结果 + */ +export const post = (url, data = {}) => { + return apiRequest(url, { + method: 'POST', + data + }) +} + +/** + * PUT请求 + * @param {string} url - 请求URL + * @param {object} data - 请求数据 + * @returns {Promise} 请求结果 + */ +export const put = (url, data = {}) => { + return apiRequest(url, { + method: 'PUT', + data + }) +} + +/** + * DELETE请求 + * @param {string} url - 请求URL + * @param {object} params - 查询参数 + * @returns {Promise} 请求结果 + */ +export const del = (url, params = {}) => { + return apiRequest(url, { + method: 'DELETE', + params + }) +} + +export default request \ No newline at end of file diff --git a/dashboard/src/api/services/config.js b/dashboard/src/api/services/config.js new file mode 100644 index 0000000..2b87f71 --- /dev/null +++ b/dashboard/src/api/services/config.js @@ -0,0 +1,415 @@ +/** + * 配置管理服务 + * 负责管理应用配置,包括配置地址、站点数据等 + * 支持localStorage持久化存储 + */ + +import axios from 'axios' + +/** + * 配置管理类 + */ +class ConfigService { + constructor() { + this.configUrl = null + this.configData = null + this.lastFetchTime = null + this.cacheExpiry = 5 * 60 * 1000 // 5分钟缓存 + this.liveConfigUrl = null // 直播配置地址 + this.loadConfigFromStorage() + } + + /** + * 设置配置地址 + * @param {string} url - 配置地址URL + * @returns {Promise} 设置是否成功 + */ + async setConfigUrl(url) { + try { + if (!url || typeof url !== 'string') { + throw new Error('配置地址不能为空') + } + + // 验证URL格式 + const urlPattern = /^https?:\/\/.+/ + if (!urlPattern.test(url)) { + throw new Error('配置地址格式不正确,请输入有效的HTTP/HTTPS地址') + } + + // 测试配置地址是否可访问 + const isValid = await this.validateConfigUrl(url) + if (!isValid) { + throw new Error('配置地址无法访问或数据格式不正确') + } + + this.configUrl = url + this.saveConfigToStorage() + + // 自动设置直播配置地址(如果未设置) + await this.autoSetLiveConfigUrl() + + console.log('配置地址设置成功:', url) + return true + } catch (error) { + console.error('设置配置地址失败:', error) + throw error + } + } + + /** + * 设置直播配置地址 + * @param {string} url - 直播配置地址URL + * @returns {Promise} 设置是否成功 + */ + async setLiveConfigUrl(url) { + try { + if (!url || typeof url !== 'string') { + throw new Error('直播配置地址不能为空') + } + + // 验证URL格式 + const urlPattern = /^https?:\/\/.+/ + if (!urlPattern.test(url)) { + throw new Error('直播配置地址格式不正确,请输入有效的HTTP/HTTPS地址') + } + + this.liveConfigUrl = url + this.saveConfigToStorage() + + console.log('直播配置地址设置成功:', url) + return true + } catch (error) { + console.error('设置直播配置地址失败:', error) + throw error + } + } + + /** + * 获取直播配置地址 + * @returns {string|null} 直播配置地址 + */ + getLiveConfigUrl() { + return this.liveConfigUrl + } + + /** + * 自动设置直播配置地址(从点播配置中提取lives链接) + * @returns {Promise} 是否成功设置 + */ + async autoSetLiveConfigUrl() { + try { + // 如果已经设置了直播配置地址,则不自动设置 + if (this.liveConfigUrl) { + return false + } + + // 获取点播配置数据 + const configData = await this.getConfigData() + if (!configData || !configData.lives || !Array.isArray(configData.lives) || configData.lives.length === 0) { + console.log('点播配置中未找到lives链接') + return false + } + + // 取lives数组中第一个对象的url字段 + const firstLiveConfig = configData.lives[0] + if (!firstLiveConfig || !firstLiveConfig.url) { + console.log('lives配置中未找到有效的url') + return false + } + + // 设置直播配置地址 + this.liveConfigUrl = firstLiveConfig.url + this.saveConfigToStorage() + + console.log('自动设置直播配置地址成功:', firstLiveConfig.url) + return true + } catch (error) { + console.error('自动设置直播配置地址失败:', error) + return false + } + } + + /** + * 重置直播配置地址(重置为当前点播配置中的默认lives链接) + * @returns {Promise} 是否成功重置 + */ + async resetLiveConfigUrl() { + try { + // 获取点播配置数据 + const configData = await this.getConfigData() + if (!configData || !configData.lives || !Array.isArray(configData.lives) || configData.lives.length === 0) { + console.log('点播配置中未找到lives链接,无法重置') + return false + } + + // 取lives数组中第一个对象的url字段 + const firstLiveConfig = configData.lives[0] + if (!firstLiveConfig || !firstLiveConfig.url) { + console.log('lives配置中未找到有效的url,无法重置') + return false + } + + // 重置直播配置地址 + this.liveConfigUrl = firstLiveConfig.url + this.saveConfigToStorage() + + console.log('直播配置地址重置成功:', firstLiveConfig.url) + return true + } catch (error) { + console.error('重置直播配置地址失败:', error) + return false + } + } + + /** + * 获取当前配置地址 + * @returns {string|null} 当前配置地址 + */ + getConfigUrl() { + return this.configUrl + } + + /** + * 验证配置地址是否有效 + * @param {string} url - 配置地址 + * @returns {Promise} 是否有效 + */ + async validateConfigUrl(url) { + try { + const response = await axios.get(url, { + timeout: 10000, + headers: { + 'Accept': 'application/json' + } + }) + + if (!response.data) { + return false + } + + // 验证数据格式是否包含必要字段 + const data = response.data + if (!data.sites || !Array.isArray(data.sites)) { + return false + } + + // 验证sites数组中的数据格式 + if (data.sites.length > 0) { + const firstSite = data.sites[0] + if (!firstSite.key || !firstSite.name || !firstSite.api) { + return false + } + } + + return true + } catch (error) { + console.error('验证配置地址失败:', error) + return false + } + } + + /** + * 获取配置数据 + * @param {boolean} forceRefresh - 是否强制刷新 + * @returns {Promise} 配置数据 + */ + async getConfigData(forceRefresh = false) { + try { + if (!this.configUrl) { + throw new Error('未设置配置地址') + } + + // 检查缓存是否有效 + const now = Date.now() + const isCacheValid = this.configData && + this.lastFetchTime && + (now - this.lastFetchTime) < this.cacheExpiry + + if (!forceRefresh && isCacheValid) { + console.log('使用缓存的配置数据') + return this.configData + } + + console.log('从配置地址获取数据:', this.configUrl) + const response = await axios.get(this.configUrl, { + timeout: 15000, + headers: { + 'Accept': 'application/json' + } + }) + + if (!response.data) { + throw new Error('配置数据为空') + } + + this.configData = response.data + this.lastFetchTime = now + this.saveConfigToStorage() + + console.log('配置数据获取成功,站点数量:', this.configData.sites?.length || 0) + return this.configData + } catch (error) { + console.error('获取配置数据失败:', error) + throw error + } + } + + /** + * 获取站点列表 + * @param {boolean} forceRefresh - 是否强制刷新 + * @returns {Promise} 站点列表 + */ + async getSites(forceRefresh = false) { + try { + const configData = await this.getConfigData(forceRefresh) + return configData.sites || [] + } catch (error) { + console.error('获取站点列表失败:', error) + throw error + } + } + + /** + * 获取推荐配置 + * @returns {Promise} 推荐配置列表 + */ + async getRecommendConfig() { + try { + const configData = await this.getConfigData() + return configData.recommend || [] + } catch (error) { + console.error('获取推荐配置失败:', error) + return [] + } + } + + /** + * 获取其他配置信息 + * @returns {Promise} 其他配置信息 + */ + async getOtherConfig() { + try { + const configData = await this.getConfigData() + const { sites, ...otherConfig } = configData + return otherConfig + } catch (error) { + console.error('获取其他配置失败:', error) + return {} + } + } + + /** + * 清除配置缓存 + */ + clearCache() { + this.configData = null + this.lastFetchTime = null + console.log('配置缓存已清除') + } + + /** + * 重置配置 + */ + resetConfig() { + this.configUrl = null + this.configData = null + this.lastFetchTime = null + this.liveConfigUrl = null + this.saveConfigToStorage() + console.log('配置已重置') + } + + /** + * 从本地存储加载配置 + */ + loadConfigFromStorage() { + try { + const configUrl = localStorage.getItem('drplayer_config_url') + const configData = localStorage.getItem('drplayer_config_data') + const lastFetchTime = localStorage.getItem('drplayer_config_fetch_time') + const liveConfigUrl = localStorage.getItem('drplayer_live_config_url') + + if (configUrl) { + this.configUrl = configUrl + } + + if (configData) { + this.configData = JSON.parse(configData) + } + + if (lastFetchTime) { + this.lastFetchTime = parseInt(lastFetchTime) + } + + if (liveConfigUrl) { + this.liveConfigUrl = liveConfigUrl + } + + console.log('从本地存储加载配置成功') + } catch (error) { + console.error('加载配置失败:', error) + } + } + + /** + * 保存配置到本地存储 + */ + saveConfigToStorage() { + try { + if (this.configUrl) { + localStorage.setItem('drplayer_config_url', this.configUrl) + } else { + localStorage.removeItem('drplayer_config_url') + } + + if (this.configData) { + localStorage.setItem('drplayer_config_data', JSON.stringify(this.configData)) + } else { + localStorage.removeItem('drplayer_config_data') + } + + if (this.lastFetchTime) { + localStorage.setItem('drplayer_config_fetch_time', this.lastFetchTime.toString()) + } else { + localStorage.removeItem('drplayer_config_fetch_time') + } + + if (this.liveConfigUrl) { + localStorage.setItem('drplayer_live_config_url', this.liveConfigUrl) + } else { + localStorage.removeItem('drplayer_live_config_url') + } + + console.log('配置保存到本地存储成功') + } catch (error) { + console.error('保存配置失败:', error) + } + } + + /** + * 获取配置状态信息 + * @returns {object} 配置状态 + */ + getConfigStatus() { + const hasValidData = this.configData && this.configData.sites && Array.isArray(this.configData.sites) + const sitesCount = hasValidData ? this.configData.sites.length : 0 + + return { + hasConfigUrl: !!this.configUrl, + hasConfigData: !!this.configData, + isValid: hasValidData, + sitesCount: sitesCount, + lastFetchTime: this.lastFetchTime, + cacheAge: this.lastFetchTime ? Date.now() - this.lastFetchTime : null, + isCacheValid: this.configData && this.lastFetchTime && + (Date.now() - this.lastFetchTime) < this.cacheExpiry, + hasLiveConfigUrl: !!this.liveConfigUrl, + liveConfigUrl: this.liveConfigUrl + } + } +} + +// 创建单例实例 +const configService = new ConfigService() + +export default configService \ No newline at end of file diff --git a/dashboard/src/api/services/index.js b/dashboard/src/api/services/index.js new file mode 100644 index 0000000..663ac5f --- /dev/null +++ b/dashboard/src/api/services/index.js @@ -0,0 +1,19 @@ +/** + * 业务服务统一入口 + * 导出所有业务服务模块 + */ + +import videoService from './video' +import siteService from './site' + +// 导出所有服务 +export { + videoService, + siteService +} + +// 默认导出服务集合 +export default { + video: videoService, + site: siteService +} \ No newline at end of file diff --git a/dashboard/src/api/services/live.js b/dashboard/src/api/services/live.js new file mode 100644 index 0000000..5dd5e20 --- /dev/null +++ b/dashboard/src/api/services/live.js @@ -0,0 +1,488 @@ +/** + * 直播数据解析服务 + * 负责解析M3U和TXT格式的直播文件 + */ + +import axios from 'axios' +import configService from './config.js' + +/** + * 直播服务类 + */ +class LiveService { + constructor() { + this.liveData = null + this.lastFetchTime = null + this.cacheExpiry = 10 * 60 * 1000 // 10分钟缓存 + } + + /** + * 获取直播配置信息 + * @returns {Promise} 直播配置信息 + */ + async getLiveConfig() { + try { + // 优先使用独立的直播配置地址 + const liveConfigUrl = configService.getLiveConfigUrl() + if (liveConfigUrl) { + return { + name: '直播配置', + url: liveConfigUrl, + type: 'live' + } + } + + // 如果没有独立的直播配置地址,尝试从点播配置中获取(向后兼容) + const configData = await configService.getConfigData() + if (configData && configData.lives && Array.isArray(configData.lives) && configData.lives.length > 0) { + return configData.lives[0] // 通常只有一个直播配置 + } + + return null + } catch (error) { + console.error('获取直播配置失败:', error) + return null + } + } + + /** + * 获取直播数据 + * @param {boolean} forceRefresh - 是否强制刷新 + * @returns {Promise} 解析后的直播数据 + */ + async getLiveData(forceRefresh = false) { + try { + // 检查缓存是否有效 + const now = Date.now() + const isCacheValid = this.liveData && + this.lastFetchTime && + (now - this.lastFetchTime) < this.cacheExpiry + + if (!forceRefresh && isCacheValid) { + console.log('使用缓存的直播数据') + return this.liveData + } + + // 获取直播配置 + const liveConfig = await this.getLiveConfig() + if (!liveConfig || !liveConfig.url) { + throw new Error('未找到直播配置或直播地址') + } + + console.log('从直播地址获取数据:', liveConfig.url) + + // 获取直播文件内容 + const response = await axios.get(liveConfig.url, { + timeout: 15000 + // 注意:浏览器环境下不能设置 User-Agent 头,浏览器会自动处理 + }) + + if (!response.data) { + throw new Error('直播数据为空') + } + + // 根据URL判断文件类型并解析 + const fileContent = response.data + let parsedData + + if (liveConfig.url.toLowerCase().includes('.m3u')) { + parsedData = this.parseM3U(fileContent, liveConfig) + } else if (liveConfig.url.toLowerCase().includes('.txt')) { + parsedData = this.parseTXT(fileContent, liveConfig) + } else { + // 尝试根据内容判断格式 + if (fileContent.includes('#EXTM3U') || fileContent.includes('#EXTINF')) { + parsedData = this.parseM3U(fileContent, liveConfig) + } else if (fileContent.includes('#genre#')) { + parsedData = this.parseTXT(fileContent, liveConfig) + } else { + throw new Error('不支持的直播文件格式') + } + } + + this.liveData = parsedData + this.lastFetchTime = now + + console.log('直播数据解析成功,分组数量:', parsedData.groups.length) + return this.liveData + } catch (error) { + console.error('获取直播数据失败:', error) + throw error + } + } + + /** + * 解析M3U格式的直播文件 + * @param {string} content - 文件内容 + * @param {object} config - 直播配置 + * @returns {object} 解析后的数据 + */ + parseM3U(content, config) { + const lines = content.split('\n').map(line => line.trim()).filter(line => line) + const groups = new Map() + const channels = [] + + let currentChannel = null + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + // 跳过空行 + if (!line) continue; + + if (line.startsWith('#EXTINF:')) { + // 解析频道信息 - 修复正则表达式来正确处理属性和频道名称 + const match = line.match(/^#EXTINF:([-\.\d]+)\s*(.*)$/); + if (match) { + const duration = match[1] + const fullStr = match[2].trim() + + // 查找最后一个逗号,分离属性和频道名称 + const lastCommaIndex = fullStr.lastIndexOf(',') + let attributesStr = '' + let displayName = fullStr + + if (lastCommaIndex > 0) { + // 检查逗号前是否有属性(包含=号) + const beforeComma = fullStr.substring(0, lastCommaIndex) + if (beforeComma.includes('=')) { + attributesStr = beforeComma + displayName = fullStr.substring(lastCommaIndex + 1).trim() + } + } + + // 解析属性 + const attributes = {} + if (attributesStr) { + // 匹配所有属性,包括 tvg-id、tvg-name、tvg-logo、group-title 等 + const attrMatches = attributesStr.matchAll(/(\w+(?:-\w+)*)="([^"]*)"/g) + for (const attrMatch of attrMatches) { + attributes[attrMatch[1]] = attrMatch[2] + } + } + + // 频道名称优先使用 tvg-name,其次使用显示名称 + const channelName = attributes['tvg-name'] || displayName + const groupName = attributes['group-title'] || '未分组' + const logoUrl = attributes['tvg-logo'] || this.generateLogoUrl(channelName, config) + + // 解析清晰度信息(从显示名称中提取) + const qualityInfo = this.extractQualityInfo(displayName) + + currentChannel = { + name: channelName, + displayName: displayName, // 保留原始显示名称 + group: groupName, + logo: logoUrl, + tvgId: attributes['tvg-id'] || '', + tvgName: attributes['tvg-name'] || channelName, + quality: qualityInfo.quality, + resolution: qualityInfo.resolution, + codec: qualityInfo.codec, + url: null + } + } + } else if (line.startsWith('http') && currentChannel) { + // 设置频道URL + currentChannel.url = line + channels.push(currentChannel) + + // 添加到分组,并处理同名频道的线路归类 + const groupName = currentChannel.group + if (!groups.has(groupName)) { + groups.set(groupName, { + name: groupName, + channels: [] + }) + } + + const group = groups.get(groupName) + + // 查找是否已存在同名频道 + const existingChannel = group.channels.find(ch => ch.name === currentChannel.name) + + if (existingChannel) { + // 如果已存在同名频道,添加为新线路 + if (!existingChannel.routes) { + // 将原有频道转换为线路1 + existingChannel.routes = [ + { + id: 1, + name: '线路1', + url: existingChannel.url, + quality: existingChannel.quality, + resolution: existingChannel.resolution, + codec: existingChannel.codec + } + ] + } + + // 添加新线路 + existingChannel.routes.push({ + id: existingChannel.routes.length + 1, + name: `线路${existingChannel.routes.length + 1}`, + url: currentChannel.url, + quality: currentChannel.quality, + resolution: currentChannel.resolution, + codec: currentChannel.codec + }) + + // 更新频道的当前线路为第一个线路 + existingChannel.currentRoute = existingChannel.routes[0] + } else { + // 新频道,直接添加 + group.channels.push(currentChannel) + } + + currentChannel = null + } + } + + return { + config: config, + groups: Array.from(groups.values()), + channels: channels, + totalChannels: channels.length + } + } + + /** + * 从显示名称中提取清晰度信息 + * @param {string} displayName - 显示名称 + * @returns {object} 清晰度信息 + */ + extractQualityInfo(displayName) { + const result = { + quality: '', + resolution: '', + codec: '' + } + + // 提取清晰度标识 + const qualityPatterns = [ + /高码/, + /超清/, + /高清/, + /标清/, + /流畅/ + ] + + // 提取分辨率信息 + const resolutionPatterns = [ + /4K/i, + /1080[pP]/, + /720[pP]/, + /576[pP]/, + /480[pP]/, + /(\d+)[pP]/ + ] + + // 提取编码格式 + const codecPatterns = [ + /HEVC/i, + /H\.?264/i, + /H\.?265/i, + /AVC/i + ] + + // 提取帧率信息 + const fpsPatterns = [ + /(\d+)[-\s]?FPS/i, + /(\d+)帧/ + ] + + // 匹配清晰度 + for (const pattern of qualityPatterns) { + const match = displayName.match(pattern) + if (match) { + result.quality = match[0] + break + } + } + + // 匹配分辨率 + for (const pattern of resolutionPatterns) { + const match = displayName.match(pattern) + if (match) { + result.resolution = match[0] + break + } + } + + // 匹配编码格式 + for (const pattern of codecPatterns) { + const match = displayName.match(pattern) + if (match) { + result.codec = match[0] + break + } + } + + // 匹配帧率 + for (const pattern of fpsPatterns) { + const match = displayName.match(pattern) + if (match) { + result.fps = match[1] || match[0] + break + } + } + + return result + } + + /** + * 解析TXT格式的直播文件 + * @param {string} content - 文件内容 + * @param {object} config - 直播配置 + * @returns {object} 解析后的数据 + */ + parseTXT(content, config) { + const lines = content.split('\n').map(line => line.trim()).filter(line => line) + const groups = new Map() + const channels = [] + let currentGroupName = '未分组' + + for (const line of lines) { + if (line.includes('#genre#')) { + // 分组标记 - 格式为 "分组名称,#genre#" + const genreIndex = line.indexOf('#genre#') + if (genreIndex > 0) { + // 提取逗号前的分组名称 + currentGroupName = line.substring(0, genreIndex).replace(/,$/, '').trim() + } else { + // 兼容其他可能的格式 + currentGroupName = line.replace('#genre#', '').trim() + } + + if (!groups.has(currentGroupName)) { + groups.set(currentGroupName, { + name: currentGroupName, + channels: [] + }) + } + } else if (line.includes(',http')) { + // 频道信息 + const parts = line.split(',') + if (parts.length >= 2) { + const name = parts[0].trim() + const url = parts.slice(1).join(',').trim() + + const channel = { + name: name, + group: currentGroupName, + logo: this.generateLogoUrl(name, config), + tvgName: name, + url: url + } + + channels.push(channel) + + // 添加到分组 + if (!groups.has(currentGroupName)) { + groups.set(currentGroupName, { + name: currentGroupName, + channels: [] + }) + } + groups.get(currentGroupName).channels.push(channel) + } + } + } + + return { + config: config, + groups: Array.from(groups.values()), + channels: channels, + totalChannels: channels.length + } + } + + /** + * 生成频道Logo URL + * @param {string} channelName - 频道名称 + * @param {object} config - 直播配置 + * @returns {string} Logo URL + */ + generateLogoUrl(channelName, config) { + if (config.logo && config.logo.includes('{name}')) { + return config.logo.replace('{name}', encodeURIComponent(channelName)) + } + return '' + } + + /** + * 获取EPG信息 + * @param {string} channelName - 频道名称 + * @param {string} date - 日期 (YYYY-MM-DD) + * @param {object} config - 直播配置 + * @returns {string} EPG URL + */ + getEPGUrl(channelName, date, config) { + if (config.epg && config.epg.includes('{name}') && config.epg.includes('{date}')) { + return config.epg + .replace('{name}', encodeURIComponent(channelName)) + .replace('{date}', date) + } + return '' + } + + /** + * 搜索频道 + * @param {string} keyword - 搜索关键词 + * @returns {Array} 匹配的频道列表 + */ + searchChannels(keyword) { + if (!this.liveData || !keyword) { + return [] + } + + const lowerKeyword = keyword.toLowerCase() + return this.liveData.channels.filter(channel => + channel.name.toLowerCase().includes(lowerKeyword) || + channel.group.toLowerCase().includes(lowerKeyword) + ) + } + + /** + * 根据分组获取频道 + * @param {string} groupName - 分组名称 + * @returns {Array} 频道列表 + */ + getChannelsByGroup(groupName) { + if (!this.liveData) { + return [] + } + + const group = this.liveData.groups.find(g => g.name === groupName) + return group ? group.channels : [] + } + + /** + * 清除缓存 + */ + clearCache() { + this.liveData = null + this.lastFetchTime = null + console.log('直播数据缓存已清除') + } + + /** + * 获取直播数据状态 + * @returns {object} 状态信息 + */ + getStatus() { + return { + hasData: !!this.liveData, + lastFetchTime: this.lastFetchTime, + cacheAge: this.lastFetchTime ? Date.now() - this.lastFetchTime : null, + isCacheValid: this.liveData && this.lastFetchTime && + (Date.now() - this.lastFetchTime) < this.cacheExpiry, + groupsCount: this.liveData ? this.liveData.groups.length : 0, + channelsCount: this.liveData ? this.liveData.totalChannels : 0 + } + } +} + +// 创建单例实例 +const liveService = new LiveService() + +export default liveService diff --git a/dashboard/src/api/services/parser.js b/dashboard/src/api/services/parser.js new file mode 100644 index 0000000..bb0e593 --- /dev/null +++ b/dashboard/src/api/services/parser.js @@ -0,0 +1,455 @@ +/** + * 解析器服务 + * 处理JSON类型和嗅探类型的解析逻辑 + */ + +import axios from 'axios' +import { API_CONFIG } from '@/api/config' + +class ParserService { + /** + * 验证解析器配置 + * @param {Object} parser - 解析器配置 + * @returns {Object} 验证结果 + */ + static validateParserConfig(parser) { + const errors = [] + + if (!parser) { + return { valid: false, errors: ['解析器配置不能为空'] } + } + + if (!parser.name) { + errors.push('解析器名称不能为空') + } + + if (!parser.url) { + errors.push('解析器URL不能为空') + } else { + // 验证URL格式 + try { + new URL(parser.url) + } catch (e) { + errors.push('解析器URL格式无效') + } + } + + if (!parser.type || !['json', 'sniffer'].includes(parser.type)) { + errors.push('解析器类型必须是 json 或 sniffer') + } + + // JSON类型特定验证 + if (parser.type === 'json') { + if (!parser.urlPath) { + errors.push('JSON解析器必须配置URL提取路径(urlPath)') + } + } + + // 嗅探类型特定验证 + if (parser.type === 'sniffer') { + // 嗅探解析器直接拼接URL,不需要占位符验证 + } + + return { + valid: errors.length === 0, + errors + } + } + + /** + * 测试解析器配置 + * @param {Object} parser - 解析器配置 + * @param {string} testUrl - 测试URL + * @returns {Promise} 测试结果 + */ + static async testParserConfig(parser, testUrl = 'https://example.com/test.mp4') { + try { + console.log('🧪 [解析器测试] 开始测试解析器配置:', { + parser: parser.name, + testUrl, + isDefaultTestUrl: testUrl === 'https://example.com/test.mp4' + }) + + // 首先验证配置 + const validation = this.validateParserConfig(parser) + if (!validation.valid) { + return { + success: false, + message: '配置验证失败: ' + validation.errors.join(', ') + } + } + + // 执行测试解析 + let result + if (parser.type === 'json') { + console.log('🧪 [解析器测试] 使用JSON解析器测试') + result = await this.parseWithJsonParser(parser, { url: testUrl }) + } else if (parser.type === 'sniffer') { + console.log('🧪 [解析器测试] 使用嗅探解析器测试') + result = await this.parseWithSnifferParser(parser, { url: testUrl }) + } + + return { + success: result.success, + message: result.success ? '解析器测试成功' : result.message, + testResult: result + } + } catch (error) { + return { + success: false, + message: '解析器测试失败: ' + error.message + } + } + } + + /** + * 使用JSON类型解析器解析视频 + * @param {Object} parser - 解析器配置 + * @param {Object} data - 需要解析的数据 + * @returns {Promise} 解析结果 + */ + static async parseWithJsonParser(parser, data) { + try { + console.log('🔍 [JSON解析] 开始解析:', { + parser: parser.name, + data, + dataType: typeof data, + isTestUrl: data && typeof data === 'object' && data.url === 'https://example.com/test.mp4' + }) + + if (!parser.url) { + throw new Error('解析器URL未配置') + } + + // 提取要解析的URL - 优先处理T4接口返回的数据结构 + let videoUrl + if (data && typeof data === 'object') { + // T4接口返回的数据结构:{ jx: 1, url: "视频地址", headers: {...} } + videoUrl = data.url || data.play_url || data + console.log('从T4数据结构提取的目标URL:', videoUrl) + } else { + // 简单字符串格式 + videoUrl = data + console.log('直接使用的目标URL:', videoUrl) + } + + // 验证URL有效性 + if (!videoUrl || typeof videoUrl !== 'string') { + throw new Error('无效的视频URL') + } + + console.log('要解析的视频URL:', videoUrl) + + // 构建完整的解析地址:解析器URL + 待解析URL + const fullParseUrl = parser.url + encodeURIComponent(videoUrl) + console.log('拼接后的解析地址:', fullParseUrl) + + // 获取代理访问接口配置 + const savedAddresses = JSON.parse(localStorage.getItem('addressSettings') || '{}') + const proxyAccessEnabled = savedAddresses.proxyAccessEnabled || false + const proxyAccess = savedAddresses.proxyAccess || '' + + let requestUrl = fullParseUrl + + // 如果启用了代理访问接口,使用代理访问链接 + if (proxyAccessEnabled && proxyAccess) { + console.log('🔄 [代理访问] 使用代理访问接口:', proxyAccess) + + if (proxyAccess.includes('${url}')) { + // 替换代理访问链接中的${url}占位符 + requestUrl = proxyAccess.replace(/\$\{url\}/g, encodeURIComponent(fullParseUrl)) + console.log('🔄 [代理访问] 替换占位符后的最终URL:', requestUrl) + } else { + console.warn('⚠️ [代理访问] 代理访问链接中未找到${url}占位符,将直接访问原地址') + } + } else { + console.log('🔄 [直接访问] 代理访问接口未启用,直接访问解析地址') + } + + // 发送解析请求 + const axiosConfig = { + method: parser.method || 'GET', + url: requestUrl, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Referer': fullParseUrl, // 使用拼接后的解析地址作为Referer + ...parser.headers + }, + timeout: API_CONFIG.TIMEOUT + } + + const response = await axios(axiosConfig) + + console.log('JSON解析响应:', response.data) + + // 解析响应数据 + const result = this.parseJsonResponse(response.data, parser) + + return { + success: true, + url: result.url, + headers: result.headers || {}, + qualities: result.qualities || [], + message: '解析成功' + } + } catch (error) { + console.error('JSON解析失败:', error) + return { + success: false, + message: error.message || 'JSON解析失败' + } + } + } + + /** + * 使用嗅探类型解析器解析视频 + * @param {Object} parser - 解析器配置 + * @param {Object} data - 需要解析的数据 + * @returns {Promise} 解析结果 + */ + static async parseWithSnifferParser(parser, data) { + try { + console.log('开始嗅探解析:', { parser: parser.name, data }) + + if (!parser.url) { + throw new Error('解析器URL未配置') + } + + // 构建嗅探URL + const sniffUrl = this.buildSnifferUrl(parser, data) + + console.log('嗅探URL:', sniffUrl) + + // 发送嗅探请求 + const response = await axios({ + method: 'GET', + url: sniffUrl, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Referer': data.referer || '', + ...parser.headers + }, + timeout: API_CONFIG.TIMEOUT, + maxRedirects: 5 + }) + + console.log('嗅探解析响应状态:', response.status) + + // 从响应中提取视频URL + const videoUrl = this.extractVideoUrlFromSniffer(response, parser) + + if (!videoUrl) { + throw new Error('未能从嗅探响应中提取到视频URL') + } + + return { + success: true, + url: videoUrl, + headers: { + 'Referer': parser.url, + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + }, + qualities: [], + message: '嗅探解析成功' + } + } catch (error) { + console.error('嗅探解析失败:', error) + return { + success: false, + message: error.message || '嗅探解析失败' + } + } + } + + /** + * 解析JSON响应数据 + * @param {Object} responseData - 响应数据 + * @param {Object} parser - 解析器配置 + * @returns {Object} 解析结果 + */ + static parseJsonResponse(responseData, parser) { + try { + let data = responseData + + // 如果响应是字符串,尝试解析为JSON + if (typeof data === 'string') { + data = JSON.parse(data) + } + + // 根据解析器配置提取数据 + const result = { + url: this.extractValueByPath(data, parser.urlPath || 'url'), + headers: {}, + qualities: [] + } + + // 提取请求头 + if (parser.headersPath) { + result.headers = this.extractValueByPath(data, parser.headersPath) || {} + } + + // 提取多画质数据 + if (parser.qualitiesPath) { + const qualities = this.extractValueByPath(data, parser.qualitiesPath) + if (Array.isArray(qualities)) { + result.qualities = qualities.map(q => ({ + name: q.name || q.quality || 'Unknown', + url: q.url || q.playUrl || q.src + })) + } + } + + return result + } catch (error) { + console.error('解析JSON响应失败:', error) + throw new Error('解析JSON响应失败: ' + error.message) + } + } + + /** + * 构建嗅探器URL + * @param {Object} parser - 解析器配置 + * @param {Object} data - 数据 + * @returns {string} 嗅探URL + */ + static buildSnifferUrl(parser, data) { + let url = parser.url + + // 提取要解析的URL - 优先处理T4接口返回的数据结构 + let videoUrl + if (data && typeof data === 'object') { + // T4接口返回的数据结构:{ jx: 1, url: "视频地址", headers: {...} } + videoUrl = data.url || data.play_url || data + console.log('从T4数据结构提取的嗅探目标URL:', videoUrl) + } else { + // 简单字符串格式 + videoUrl = data + console.log('直接使用的嗅探目标URL:', videoUrl) + } + + // 验证URL有效性 + if (!videoUrl || typeof videoUrl !== 'string') { + throw new Error('无效的视频URL') + } + + // 直接将解析器URL与待解析URL相加 + if (url.includes('{url}')) { + // 如果包含占位符,替换它(兼容旧格式) + url = url.replace(/\{url\}/g, encodeURIComponent(videoUrl)) + } else { + // 直接字符串相加:解析器URL + 待解析URL + url = url + encodeURIComponent(videoUrl) + } + + // 替换时间占位符 + url = url.replace(/\{time\}/g, Date.now()) + + // 添加额外参数 + if (parser.params) { + const params = new URLSearchParams() + Object.entries(parser.params).forEach(([key, value]) => { + params.append(key, value) + }) + url += (url.includes('?') ? '&' : '?') + params.toString() + } + + return url + } + + /** + * 从嗅探响应中提取视频URL + * @param {Object} response - HTTP响应 + * @param {Object} parser - 解析器配置 + * @returns {string|null} 视频URL + */ + static extractVideoUrlFromSniffer(response, parser) { + try { + // 方法1: 从响应头的Location中获取 + if (response.headers.location) { + const location = response.headers.location + if (this.isVideoUrl(location)) { + return location + } + } + + // 方法2: 从响应体中提取 + if (response.data) { + let content = response.data + + // 如果是JSON响应 + if (typeof content === 'object') { + const url = this.extractValueByPath(content, parser.urlPath || 'url') + if (url && this.isVideoUrl(url)) { + return url + } + } + + // 如果是HTML响应,使用正则提取 + if (typeof content === 'string') { + const videoUrlRegex = /(https?:\/\/[^\s"'<>]+\.(?:mp4|m3u8|flv|avi|mkv|mov|wmv|webm)(?:\?[^\s"'<>]*)?)/gi + const matches = content.match(videoUrlRegex) + if (matches && matches.length > 0) { + return matches[0] + } + } + } + + // 方法3: 如果配置了自定义提取规则 + if (parser.extractRule) { + const regex = new RegExp(parser.extractRule, 'gi') + const matches = response.data.match(regex) + if (matches && matches.length > 0) { + return matches[0] + } + } + + return null + } catch (error) { + console.error('提取视频URL失败:', error) + return null + } + } + + /** + * 根据路径提取对象中的值 + * @param {Object} obj - 对象 + * @param {string} path - 路径,如 'data.url' 或 'result[0].playUrl' + * @returns {any} 提取的值 + */ + static extractValueByPath(obj, path) { + try { + return path.split('.').reduce((current, key) => { + // 处理数组索引,如 result[0] + const arrayMatch = key.match(/^(\w+)\[(\d+)\]$/) + if (arrayMatch) { + const [, arrayKey, index] = arrayMatch + return current?.[arrayKey]?.[parseInt(index)] + } + return current?.[key] + }, obj) + } catch (error) { + console.error('提取路径值失败:', error, { path, obj }) + return null + } + } + + /** + * 检查URL是否为视频URL + * @param {string} url - URL + * @returns {boolean} 是否为视频URL + */ + static isVideoUrl(url) { + if (!url || typeof url !== 'string') { + return false + } + + const videoExtensions = ['.mp4', '.m3u8', '.flv', '.avi', '.mkv', '.mov', '.wmv', '.webm'] + const lowerUrl = url.toLowerCase() + + return videoExtensions.some(ext => lowerUrl.includes(ext)) || + lowerUrl.includes('video') || + lowerUrl.includes('stream') + } +} + +export default ParserService \ No newline at end of file diff --git a/dashboard/src/api/services/recommendService.js b/dashboard/src/api/services/recommendService.js new file mode 100644 index 0000000..ce05cc3 --- /dev/null +++ b/dashboard/src/api/services/recommendService.js @@ -0,0 +1,513 @@ +/** + * 推荐服务 + * 管理内容推荐和热搜数据 + */ + +// 热门推荐数据 +const HOT_RECOMMENDATIONS = { + movies: [ + { + id: 'movie_1', + title: '流浪地球2', + type: '电影', + category: '科幻', + rating: 9.2, + year: 2023, + description: '太阳即将毁灭,人类在地球表面建造出巨大的推进器,寻找新的家园。', + poster: 'https://img.alicdn.com/imgextra/i1/O1CN01qJdqKd1Ks8zNjp3wP_!!6000000001218-0-tps-300-400.jpg', + tags: ['科幻', '灾难', '中国'], + hotScore: 95, + trending: true + }, + { + id: 'movie_2', + title: '满江红', + type: '电影', + category: '剧情', + rating: 8.8, + year: 2023, + description: '南宋绍兴年间,岳飞死后四年,秦桧率兵与金国会谈。', + poster: 'https://img.alicdn.com/imgextra/i2/O1CN01YjQKjC1Ks8zNjp3wQ_!!6000000001218-0-tps-300-400.jpg', + tags: ['剧情', '历史', '悬疑'], + hotScore: 88, + trending: true + }, + { + id: 'movie_3', + title: '深海', + type: '电影', + category: '动画', + rating: 8.5, + year: 2023, + description: '一个现代女孩意外进入梦幻的深海世界。', + poster: 'https://img.alicdn.com/imgextra/i3/O1CN01YjQKjC1Ks8zNjp3wR_!!6000000001218-0-tps-300-400.jpg', + tags: ['动画', '奇幻', '治愈'], + hotScore: 82, + trending: false + }, + { + id: 'movie_4', + title: '阿凡达:水之道', + type: '电影', + category: '科幻', + rating: 8.9, + year: 2022, + description: '杰克·萨利一家在潘多拉星球上的新冒险。', + poster: 'https://img.alicdn.com/imgextra/i4/O1CN01YjQKjC1Ks8zNjp3wS_!!6000000001218-0-tps-300-400.jpg', + tags: ['科幻', '冒险', '3D'], + hotScore: 90, + trending: false + } + ], + tvShows: [ + { + id: 'tv_1', + title: '狂飙', + type: '电视剧', + category: '犯罪', + rating: 9.1, + year: 2023, + description: '一线刑警安欣和黑恶势力的较量。', + poster: 'https://img.alicdn.com/imgextra/i1/O1CN01qJdqKd1Ks8zNjp3wT_!!6000000001218-0-tps-300-400.jpg', + tags: ['犯罪', '悬疑', '国产'], + hotScore: 96, + trending: true, + episodes: 39 + }, + { + id: 'tv_2', + title: '三体', + type: '电视剧', + category: '科幻', + rating: 8.7, + year: 2023, + description: '基于刘慈欣同名科幻小说改编。', + poster: 'https://img.alicdn.com/imgextra/i2/O1CN01YjQKjC1Ks8zNjp3wU_!!6000000001218-0-tps-300-400.jpg', + tags: ['科幻', '悬疑', '国产'], + hotScore: 89, + trending: true, + episodes: 30 + }, + { + id: 'tv_3', + title: '庆余年2', + type: '电视剧', + category: '古装', + rating: 8.4, + year: 2024, + description: '范闲的新冒险继续展开。', + poster: 'https://img.alicdn.com/imgextra/i3/O1CN01YjQKjC1Ks8zNjp3wV_!!6000000001218-0-tps-300-400.jpg', + tags: ['古装', '喜剧', '权谋'], + hotScore: 85, + trending: true, + episodes: 36 + }, + { + id: 'tv_4', + title: '漫长的季节', + type: '电视剧', + category: '悬疑', + rating: 9.4, + year: 2023, + description: '一个跨越时空的悬疑故事。', + poster: 'https://img.alicdn.com/imgextra/i4/O1CN01YjQKjC1Ks8zNjp3wW_!!6000000001218-0-tps-300-400.jpg', + tags: ['悬疑', '剧情', '国产'], + hotScore: 94, + trending: false, + episodes: 12 + } + ], + anime: [ + { + id: 'anime_1', + title: '鬼灭之刃 锻刀村篇', + type: '动漫', + category: '热血', + rating: 9.3, + year: 2023, + description: '炭治郎前往锻刀村修复日轮刀。', + poster: 'https://img.alicdn.com/imgextra/i1/O1CN01qJdqKd1Ks8zNjp3wX_!!6000000001218-0-tps-300-400.jpg', + tags: ['热血', '战斗', '日本'], + hotScore: 93, + trending: true, + episodes: 11 + }, + { + id: 'anime_2', + title: '斗罗大陆', + type: '动漫', + category: '玄幻', + rating: 8.6, + year: 2023, + description: '唐三在斗罗大陆的修炼之路。', + poster: 'https://img.alicdn.com/imgextra/i2/O1CN01YjQKjC1Ks8zNjp3wY_!!6000000001218-0-tps-300-400.jpg', + tags: ['玄幻', '冒险', '国产'], + hotScore: 86, + trending: true, + episodes: 200 + }, + { + id: 'anime_3', + title: '咒术回战 第二季', + type: '动漫', + category: '超自然', + rating: 9.0, + year: 2023, + description: '虎杖悠仁与咒灵的战斗继续。', + poster: 'https://img.alicdn.com/imgextra/i3/O1CN01YjQKjC1Ks8zNjp3wZ_!!6000000001218-0-tps-300-400.jpg', + tags: ['超自然', '战斗', '日本'], + hotScore: 90, + trending: true, + episodes: 23 + }, + { + id: 'anime_4', + title: '间谍过家家', + type: '动漫', + category: '喜剧', + rating: 8.8, + year: 2023, + description: '间谍、杀手和超能力者组成的伪装家庭。', + poster: 'https://img.alicdn.com/imgextra/i4/O1CN01YjQKjC1Ks8zNjp3wa_!!6000000001218-0-tps-300-400.jpg', + tags: ['喜剧', '日常', '温馨'], + hotScore: 88, + trending: false, + episodes: 25 + } + ], + novels: [ + { + id: 'novel_1', + title: '三体', + type: '小说', + category: '科幻', + rating: 9.5, + year: 2006, + description: '刘慈欣的科幻巨作,探讨文明与宇宙的终极问题。', + author: '刘慈欣', + tags: ['科幻', '硬科幻', '获奖'], + hotScore: 95, + trending: true, + status: '完结' + }, + { + id: 'novel_2', + title: '庆余年', + type: '小说', + category: '穿越', + rating: 8.9, + year: 2007, + description: '现代青年穿越到古代的权谋故事。', + author: '猫腻', + tags: ['穿越', '权谋', '古代'], + hotScore: 89, + trending: true, + status: '完结' + }, + { + id: 'novel_3', + title: '斗破苍穹', + type: '小说', + category: '玄幻', + rating: 8.5, + year: 2009, + description: '萧炎的修炼逆袭之路。', + author: '天蚕土豆', + tags: ['玄幻', '修炼', '热血'], + hotScore: 85, + trending: false, + status: '完结' + }, + { + id: 'novel_4', + title: '诡秘之主', + type: '小说', + category: '克苏鲁', + rating: 9.2, + year: 2018, + description: '蒸汽与机械的时代,神秘学的世界。', + author: '爱潜水的乌贼', + tags: ['克苏鲁', '神秘', '蒸汽朋克'], + hotScore: 92, + trending: true, + status: '完结' + } + ] +} + +// 热搜关键词 +const HOT_KEYWORDS = [ + { keyword: '流浪地球2', count: 15420, trend: 'up' }, + { keyword: '狂飙', count: 12890, trend: 'up' }, + { keyword: '三体', count: 11560, trend: 'stable' }, + { keyword: '鬼灭之刃', count: 9870, trend: 'up' }, + { keyword: '庆余年2', count: 8940, trend: 'up' }, + { keyword: '满江红', count: 7650, trend: 'down' }, + { keyword: '漫长的季节', count: 6780, trend: 'stable' }, + { keyword: '咒术回战', count: 5890, trend: 'up' }, + { keyword: '间谍过家家', count: 4560, trend: 'stable' }, + { keyword: '诡秘之主', count: 3890, trend: 'up' } +] + +/** + * 获取所有推荐内容 + */ +export const getAllRecommendations = () => { + return HOT_RECOMMENDATIONS +} + +/** + * 根据类型获取推荐内容 + */ +export const getRecommendationsByType = (type) => { + return HOT_RECOMMENDATIONS[type] || [] +} + +/** + * 获取热门推荐(综合所有类型) + */ +export const getHotRecommendations = (limit = 12) => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + return allItems + .sort((a, b) => b.hotScore - a.hotScore) + .slice(0, limit) +} + +/** + * 获取趋势推荐(正在热播/热门) + */ +export const getTrendingRecommendations = (limit = 8) => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + return allItems + .filter(item => item.trending) + .sort((a, b) => b.hotScore - a.hotScore) + .slice(0, limit) +} + +/** + * 根据分类获取推荐 + */ +export const getRecommendationsByCategory = (category, limit = 6) => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + return allItems + .filter(item => item.category === category) + .sort((a, b) => b.hotScore - a.hotScore) + .slice(0, limit) +} + +/** + * 根据标签获取推荐 + */ +export const getRecommendationsByTag = (tag, limit = 6) => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + return allItems + .filter(item => item.tags.includes(tag)) + .sort((a, b) => b.hotScore - a.hotScore) + .slice(0, limit) +} + +/** + * 搜索推荐内容 + */ +export const searchRecommendations = (keyword) => { + const lowerKeyword = keyword.toLowerCase() + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + return allItems + .filter(item => { + return ( + item.title.toLowerCase().includes(lowerKeyword) || + item.description.toLowerCase().includes(lowerKeyword) || + item.tags.some(tag => tag.toLowerCase().includes(lowerKeyword)) || + (item.author && item.author.toLowerCase().includes(lowerKeyword)) + ) + }) + .sort((a, b) => b.hotScore - a.hotScore) +} + +/** + * 获取所有热搜关键词 + */ +export const getAllKeywords = () => { + return HOT_KEYWORDS.sort((a, b) => b.count - a.count) +} + +/** + * 获取热搜关键词 + */ +export const getHotKeywords = (limit = 10) => { + return HOT_KEYWORDS + .sort((a, b) => b.count - a.count) + .slice(0, limit) +} + +/** + * 获取上升趋势的关键词 + */ +export const getTrendingUpKeywords = (limit = 5) => { + return HOT_KEYWORDS + .filter(item => item.trend === 'up') + .sort((a, b) => b.count - a.count) + .slice(0, limit) +} + +/** + * 基于用户观看历史的个性化推荐 + */ +export const getPersonalizedRecommendations = (watchHistory = [], limit = 8) => { + // 分析用户偏好 + const preferences = analyzeUserPreferences(watchHistory) + + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + // 根据偏好计算推荐分数 + const scoredItems = allItems.map(item => { + let score = item.hotScore + + // 类型偏好加分 + if (preferences.types[item.type]) { + score += preferences.types[item.type] * 10 + } + + // 分类偏好加分 + if (preferences.categories[item.category]) { + score += preferences.categories[item.category] * 15 + } + + // 标签偏好加分 + item.tags.forEach(tag => { + if (preferences.tags[tag]) { + score += preferences.tags[tag] * 5 + } + }) + + return { ...item, recommendScore: score } + }) + + return scoredItems + .sort((a, b) => b.recommendScore - a.recommendScore) + .slice(0, limit) +} + +/** + * 分析用户偏好 + */ +const analyzeUserPreferences = (watchHistory) => { + const preferences = { + types: {}, + categories: {}, + tags: {} + } + + // 这里可以根据实际的观看历史数据进行分析 + // 目前返回默认偏好 + return { + types: { '电视剧': 3, '动漫': 2, '电影': 2, '小说': 1 }, + categories: { '科幻': 3, '悬疑': 2, '热血': 2, '剧情': 1 }, + tags: { '国产': 2, '日本': 1, '热血': 2, '科幻': 3 } + } +} + +/** + * 获取推荐统计信息 + */ +export const getRecommendationStats = () => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + // 按类型统计 + const typeStats = {} + Object.keys(HOT_RECOMMENDATIONS).forEach(type => { + typeStats[type] = HOT_RECOMMENDATIONS[type].length + }) + + // 按分类统计 + const categoryStats = {} + allItems.forEach(item => { + categoryStats[item.category] = (categoryStats[item.category] || 0) + 1 + }) + + // 趋势统计 + const trendingCount = allItems.filter(item => item.trending).length + const averageRating = allItems.reduce((sum, item) => sum + item.rating, 0) / allItems.length + + return { + total: allItems.length, + byType: typeStats, + byCategory: categoryStats, + trending: trendingCount, + averageRating: Math.round(averageRating * 10) / 10, + hotKeywords: HOT_KEYWORDS.length + } +} + +/** + * 获取随机推荐 + */ +export const getRandomRecommendations = (limit = 6) => { + const allItems = [ + ...HOT_RECOMMENDATIONS.movies, + ...HOT_RECOMMENDATIONS.tvShows, + ...HOT_RECOMMENDATIONS.anime, + ...HOT_RECOMMENDATIONS.novels + ] + + // 随机打乱数组 + const shuffled = allItems.sort(() => 0.5 - Math.random()) + return shuffled.slice(0, limit) +} + +// 默认导出服务对象 +export default { + getAllRecommendations, + getRecommendationsByType, + getHotRecommendations, + getTrendingRecommendations, + getRecommendationsByCategory, + getRecommendationsByTag, + searchRecommendations, + getAllKeywords, + getHotKeywords, + getTrendingUpKeywords, + getPersonalizedRecommendations, + getRecommendationStats, + getRandomRecommendations +} \ No newline at end of file diff --git a/dashboard/src/api/services/site.js b/dashboard/src/api/services/site.js new file mode 100644 index 0000000..1f09438 --- /dev/null +++ b/dashboard/src/api/services/site.js @@ -0,0 +1,574 @@ +/** + * 站点管理相关业务接口服务 + * 封装站点配置、管理和数据源切换功能 + */ + +import { proxyRequest } from '../modules/proxy' +import { validateModule } from '../utils' +import { createSiteInfo } from '../types' +import configService from './config' + +/** + * 站点服务类 + */ +class SiteService { + constructor() { + this.sites = new Map() + this.currentSite = null + this.loadSitesFromStorage() + this.initializeFromConfig() + } + + /** + * 获取所有站点配置 + * @returns {Array} 站点列表 + */ + getAllSites() { + return Array.from(this.sites.values()) + } + + /** + * 根据key获取站点信息 + * @param {string} siteKey - 站点标识 + * @returns {object|null} 站点信息 + */ + getSiteByKey(siteKey) { + return this.sites.get(siteKey) || null + } + + /** + * 获取当前选中的站点 + * @returns {object|null} 当前站点信息 + */ + getCurrentSite() { + return this.currentSite + } + + /** + * 设置当前站点 + * @param {string} siteKey - 站点标识 + * @returns {boolean} 设置是否成功 + */ + setCurrentSite(siteKey) { + const site = this.sites.get(siteKey) + if (!site) { + console.error('站点不存在:', siteKey) + return false + } + + this.currentSite = site + this.saveSitesToStorage() + + // 触发站点切换事件 + this.emitSiteChange(site) + + return true + } + + /** + * 添加站点 + * @param {object} siteInfo - 站点信息 + * @returns {boolean} 添加是否成功 + */ + addSite(siteInfo) { + try { + const site = this.formatSiteInfo(siteInfo) + + if (!site.key) { + throw new Error('站点标识不能为空') + } + + if (!site.name) { + throw new Error('站点名称不能为空') + } + + if (!site.api) { + throw new Error('API地址不能为空') + } + + this.sites.set(site.key, site) + this.saveSitesToStorage() + + console.log('添加站点成功:', site.name) + return true + } catch (error) { + console.error('添加站点失败:', error) + return false + } + } + + /** + * 更新站点信息 + * @param {string} siteKey - 站点标识 + * @param {object} updates - 更新的信息 + * @returns {boolean} 更新是否成功 + */ + updateSite(siteKey, updates) { + const site = this.sites.get(siteKey) + if (!site) { + console.error('站点不存在:', siteKey) + return false + } + + try { + const updatedSite = { ...site, ...updates } + const formattedSite = this.formatSiteInfo(updatedSite) + + this.sites.set(siteKey, formattedSite) + this.saveSitesToStorage() + + // 如果更新的是当前站点,同步更新 + if (this.currentSite && this.currentSite.key === siteKey) { + this.currentSite = formattedSite + } + + console.log('更新站点成功:', formattedSite.name) + return true + } catch (error) { + console.error('更新站点失败:', error) + return false + } + } + + /** + * 删除站点 + * @param {string} siteKey - 站点标识 + * @returns {boolean} 删除是否成功 + */ + removeSite(siteKey) { + const site = this.sites.get(siteKey) + if (!site) { + console.error('站点不存在:', siteKey) + return false + } + + this.sites.delete(siteKey) + this.saveSitesToStorage() + + // 如果删除的是当前站点,清空当前站点 + if (this.currentSite && this.currentSite.key === siteKey) { + this.currentSite = null + } + + console.log('删除站点成功:', site.name) + return true + } + + /** + * 批量导入站点 + * @param {Array} siteList - 站点列表 + * @returns {object} 导入结果 + */ + importSites(siteList) { + if (!Array.isArray(siteList)) { + throw new Error('站点列表必须是数组') + } + + const result = { + success: 0, + failed: 0, + errors: [] + } + + siteList.forEach((siteInfo, index) => { + try { + const success = this.addSite(siteInfo) + if (success) { + result.success++ + } else { + result.failed++ + result.errors.push(`第${index + 1}个站点添加失败`) + } + } catch (error) { + result.failed++ + result.errors.push(`第${index + 1}个站点添加失败: ${error.message}`) + } + }) + + console.log('批量导入站点完成:', result) + return result + } + + /** + * 导出站点配置 + * @returns {Array} 站点配置列表 + */ + exportSites() { + return this.getAllSites() + } + + /** + * 测试站点连接 + * @param {string} siteKey - 站点标识 + * @returns {Promise} 测试结果 + */ + async testSiteConnection(siteKey) { + const site = this.sites.get(siteKey) + if (!site) { + throw new Error('站点不存在') + } + + try { + // 尝试获取站点首页数据 + const response = await proxyRequest(site.key, '', { + method: 'GET', + params: { test: true } + }) + + return { + success: true, + message: '连接成功', + responseTime: Date.now(), + data: response + } + } catch (error) { + return { + success: false, + message: error.message || '连接失败', + error: error + } + } + } + + /** + * 获取站点统计信息 + * @returns {object} 统计信息 + */ + getSiteStats() { + const sites = this.getAllSites() + + return { + total: sites.length, + searchable: sites.filter(site => site.searchable).length, + filterable: sites.filter(site => site.filterable).length, + quickSearch: sites.filter(site => site.quickSearch).length, + byType: this.groupSitesByType(sites) + } + } + + /** + * 按类型分组站点 + * @param {Array} sites - 站点列表 + * @returns {object} 分组结果 + */ + groupSitesByType(sites) { + const groups = {} + + sites.forEach(site => { + const type = site.type || 0 + if (!groups[type]) { + groups[type] = [] + } + groups[type].push(site) + }) + + return groups + } + + /** + * 搜索站点 + * @param {string} keyword - 搜索关键词 + * @returns {Array} 搜索结果 + */ + searchSites(keyword) { + if (!keyword || keyword.trim().length === 0) { + return this.getAllSites() + } + + const searchTerm = keyword.trim().toLowerCase() + + return this.getAllSites().filter(site => + site.name.toLowerCase().includes(searchTerm) || + site.key.toLowerCase().includes(searchTerm) || + (site.api && site.api.toLowerCase().includes(searchTerm)) + ) + } + + /** + * 格式化站点信息 + * @param {object} rawSite - 原始站点数据 + * @returns {object} 格式化后的站点信息 + */ + formatSiteInfo(rawSite) { + const site = createSiteInfo() + + Object.keys(site).forEach(key => { + if (rawSite[key] !== undefined) { + site[key] = rawSite[key] + } + }) + + // 处理特殊字段 + site.searchable = rawSite.searchable ? 1 : 0 + site.quickSearch = rawSite.quickSearch ? 1 : 0 + site.filterable = rawSite.filterable ? 1 : 0 + site.type = parseInt(rawSite.type) || 0 + site.order = parseInt(rawSite.order) || 0 + + return site + } + + /** + * 从本地存储加载站点配置 + */ + loadSitesFromStorage() { + try { + const sitesData = localStorage.getItem('drplayer_sites') + const currentSiteKey = localStorage.getItem('drplayer_current_site') + + if (sitesData) { + const sites = JSON.parse(sitesData) + sites.forEach(site => { + this.sites.set(site.key, site) + }) + } + + if (currentSiteKey) { + this.currentSite = this.sites.get(currentSiteKey) + } + + console.log('从本地存储加载站点配置成功') + } catch (error) { + console.error('加载站点配置失败:', error) + } + } + + /** + * 保存站点配置到本地存储 + */ + saveSitesToStorage() { + try { + const sites = this.getAllSites() + localStorage.setItem('drplayer_sites', JSON.stringify(sites)) + + if (this.currentSite) { + localStorage.setItem('drplayer_current_site', this.currentSite.key) + } else { + localStorage.removeItem('drplayer_current_site') + } + + console.log('保存站点配置到本地存储成功') + } catch (error) { + console.error('保存站点配置失败:', error) + } + } + + /** + * 触发站点切换事件 + * @param {object} site - 新的站点信息 + */ + emitSiteChange(site) { + // 可以在这里添加事件监听器机制 + console.log('站点已切换:', site.name) + + // 触发自定义事件 + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent('siteChange', { + detail: { site } + })) + } + } + + /** + * 清空所有站点 + */ + clearAllSites() { + this.sites.clear() + this.currentSite = null + this.saveSitesToStorage() + console.log('已清空所有站点配置') + } + + /** + * 从配置服务初始化站点数据 + */ + async initializeFromConfig() { + try { + const configStatus = configService.getConfigStatus() + if (configStatus.hasConfigUrl) { + await this.loadSitesFromConfig() + } + } catch (error) { + console.error('从配置服务初始化失败:', error) + } + } + + /** + * 从配置地址加载站点数据 + * @param {boolean} forceRefresh - 是否强制刷新 + * @returns {Promise} 站点列表 + */ + async loadSitesFromConfig(forceRefresh = false) { + try { + const sites = await configService.getSites(forceRefresh) + + if (sites && sites.length > 0) { + // 保存当前站点信息用于智能切换 + const previousCurrentSite = this.currentSite + + // 清空现有站点(保留本地添加的站点) + const localSites = Array.from(this.sites.values()).filter(site => site.isLocal) + this.sites.clear() + + // 重新添加本地站点 + localSites.forEach(site => { + this.sites.set(site.key, site) + }) + + // 添加配置中的站点 + sites.forEach(siteData => { + const siteInfo = this.formatSiteInfo(siteData) + siteInfo.isFromConfig = true // 标记为来自配置 + this.sites.set(siteInfo.key, siteInfo) + }) + + // 智能源切换逻辑 + this.handleSmartSiteSwitch(previousCurrentSite) + + this.saveSitesToStorage() + console.log(`从配置加载了 ${sites.length} 个站点`) + + // 触发站点更新事件 + this.emitSitesUpdate() + + return sites + } + + return [] + } catch (error) { + console.error('从配置加载站点失败:', error) + throw error + } + } + + /** + * 刷新配置数据 + * @returns {Promise} 更新后的站点列表 + */ + async refreshConfig() { + try { + return await this.loadSitesFromConfig(true) + } catch (error) { + console.error('刷新配置失败:', error) + throw error + } + } + + /** + * 获取配置状态 + * @returns {object} 配置状态信息 + */ + getConfigStatus() { + return configService.getConfigStatus() + } + + /** + * 设置配置地址 + * @param {string} url - 配置地址 + * @returns {Promise} 设置是否成功 + */ + async setConfigUrl(url) { + try { + const success = await configService.setConfigUrl(url) + if (success) { + // 配置地址设置成功后,立即加载站点数据 + await this.loadSitesFromConfig(true) + } + return success + } catch (error) { + console.error('设置配置地址失败:', error) + throw error + } + } + + /** + * 获取当前配置地址 + * @returns {string|null} 配置地址 + */ + getConfigUrl() { + return configService.getConfigUrl() + } + + /** + * 触发站点更新事件 + */ + emitSitesUpdate() { + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent('sitesUpdate', { + detail: { + sites: this.getAllSites(), + count: this.sites.size + } + })) + } + } + + /** + * 智能站点切换处理 + * @param {object} previousCurrentSite - 之前的当前站点 + */ + handleSmartSiteSwitch(previousCurrentSite) { + let needReload = false + + if (previousCurrentSite && previousCurrentSite.key) { + // 检查之前的当前站点是否还在新的站点列表中 + const siteStillExists = this.sites.has(previousCurrentSite.key) + + if (siteStillExists) { + // 如果站点仍然存在,保持当前选择 + this.currentSite = this.sites.get(previousCurrentSite.key) + console.log('保持当前源:', this.currentSite.name) + } else { + // 如果站点不存在,切换到第一个可用站点 + const availableSites = this.getAllSites() + if (availableSites.length > 0) { + // 优先选择type为4的站点,如果没有则选择第一个 + const firstSite = availableSites.find(site => site.type === 4) || availableSites[0] + this.currentSite = firstSite + needReload = true + console.log('自动切换到新源:', this.currentSite.name) + + // 触发站点切换事件 + this.emitSiteChange(this.currentSite) + } + } + } else { + // 如果之前没有当前站点,设置第一个可用站点 + const availableSites = this.getAllSites() + if (availableSites.length > 0) { + const firstSite = availableSites.find(site => site.type === 4) || availableSites[0] + this.currentSite = firstSite + needReload = true + console.log('设置默认源:', this.currentSite.name) + + // 触发站点切换事件 + this.emitSiteChange(this.currentSite) + } + } + + // 如果需要重载,触发重载源事件 + if (needReload) { + this.emitReloadSource() + } + } + + /** + * 触发重载源事件 + */ + emitReloadSource() { + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent('reloadSource', { + detail: { + site: this.currentSite, + timestamp: Date.now() + } + })) + console.log('触发重载源事件') + } + } +} + +// 创建单例实例 +const siteService = new SiteService() + +export default siteService \ No newline at end of file diff --git a/dashboard/src/api/services/sniffer.js b/dashboard/src/api/services/sniffer.js new file mode 100644 index 0000000..e144b61 --- /dev/null +++ b/dashboard/src/api/services/sniffer.js @@ -0,0 +1,231 @@ +/** + * 嗅探服务 API + * 用于调用代理嗅探接口进行视频链接嗅探 + */ + +/** + * 调用嗅探接口 + * @param {string|Object} urlOrParseData - 要嗅探的页面URL或T4解析数据 + * @param {Object} options - 嗅探选项 + * @param {string} options.snifferUrl - 嗅探器接口地址 + * @param {number} options.timeout - 嗅探器服务端超时时间(秒),来自设置页面的"嗅探超时"配置 + * @param {string} options.mode - 嗅探模式:0=单个链接,1=多个链接 + * @param {string} options.is_pc - 设备模拟:0=移动设备,1=PC + * @returns {Promise} 嗅探结果 + */ +export const sniffVideo = async (urlOrParseData, options = {}) => { + const { + snifferUrl = 'http://localhost:57573/sniffer', + timeout = 10, + mode = '0', + is_pc = '0' + } = options + + if (!urlOrParseData) { + throw new Error('URL或解析数据参数不能为空') + } + + if (!snifferUrl) { + throw new Error('嗅探器接口地址不能为空') + } + + try { + let requestUrl + + // 检查是否是T4解析数据格式 + if (typeof urlOrParseData === 'object' && urlOrParseData.parse === 1) { + // T4解析数据格式:{ parse: 1, url: string, js: string, parse_extra: string } + const { url, js, parse_extra } = urlOrParseData + + if (!url) { + throw new Error('T4解析数据中缺少URL') + } + + // 确保URL是字符串格式 + const urlString = typeof url === 'string' ? url : (url.toString ? url.toString() : String(url)) + + console.log('处理T4解析数据:', urlOrParseData) + console.log('提取的URL:', urlString) + console.log('=== 调试结束 ===') + + // 构建基础参数 + const params = new URLSearchParams({ + url: urlString, + mode: mode, + is_pc: is_pc, + timeout: (timeout * 1000).toString() // 嗅探器服务端超时时间(毫秒) + }) + + // 添加脚本参数 + if (js) { + params.set('script', js) + } + + // 构建请求URL + requestUrl = `${snifferUrl}?${params.toString()}` + + // 添加额外参数(parse_extra) + if (parse_extra) { + // parse_extra 通常以 & 开头,直接拼接 + requestUrl += parse_extra + } + + } else { + // 普通URL格式 + const url = typeof urlOrParseData === 'string' ? urlOrParseData : urlOrParseData.toString() + + // 构建请求参数 + const params = new URLSearchParams({ + url: url, + mode: mode, + is_pc: is_pc, + timeout: (timeout * 1000).toString() // 嗅探器服务端超时时间(毫秒) + }) + + requestUrl = `${snifferUrl}?${params.toString()}` + } + + console.log('嗅探请求URL:', requestUrl) + + // 发起嗅探请求,设置客户端超时 + const controller = new AbortController() + // 客户端超时 = 嗅探器超时 + 5秒网络缓冲时间,确保嗅探器有足够时间完成工作 + const timeoutId = setTimeout(() => controller.abort(), timeout * 1000 + 5000) + + const response = await fetch(requestUrl, { + method: 'GET', + signal: controller.signal, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + throw new Error(`嗅探请求失败: ${response.status} ${response.statusText}`) + } + + const result = await response.json() + console.log('嗅探响应结果:', result) + + // 检查响应格式 + if (result.code !== 200) { + throw new Error(result.msg || '嗅探失败') + } + + // 返回标准化的结果 + return { + success: true, + data: result.data, + message: result.msg || '嗅探成功', + timestamp: result.timestamp + } + + } catch (error) { + console.error('嗅探请求失败:', error) + + // 处理不同类型的错误 + if (error.name === 'AbortError') { + throw new Error(`嗅探超时(${timeout}秒)`) + } else if (error.message.includes('Failed to fetch')) { + throw new Error('无法连接到嗅探器服务,请检查嗅探器是否正常运行') + } else { + throw error + } + } +} + +/** + * 从本地存储获取嗅探器配置 + * @returns {Object} 嗅探器配置 + */ +export const getSnifferConfig = () => { + try { + const savedAddresses = localStorage.getItem('addressSettings') + if (savedAddresses) { + const parsed = JSON.parse(savedAddresses) + return { + enabled: parsed.proxySniffEnabled || false, + url: parsed.proxySniff || 'http://localhost:57573/sniffer', + timeout: parsed.snifferTimeout || 10 + } + } + } catch (error) { + console.error('获取嗅探器配置失败:', error) + } + + // 返回默认配置 + return { + enabled: false, + url: 'http://localhost:57573/sniffer', + timeout: 10 + } +} + +/** + * 检查嗅探器是否启用 + * @returns {boolean} 是否启用 + */ +export const isSnifferEnabled = () => { + const config = getSnifferConfig() + + // 检查URL是否有效 + const hasValidUrl = config.url && config.url.trim() !== '' && config.url !== 'undefined' + + if (!hasValidUrl) { + return false + } + + // 检查是否有保存的设置 + try { + const savedAddresses = localStorage.getItem('addressSettings') + if (savedAddresses) { + const parsed = JSON.parse(savedAddresses) + // 如果用户明确保存了设置,使用保存的enabled状态 + return parsed.proxySniffEnabled === true + } + } catch (error) { + console.error('检查嗅探器配置失败:', error) + } + + // 如果没有保存的设置,但有有效的URL(包括默认URL),认为嗅探可用 + // 这样可以处理用户打开开关但未保存设置的情况 + return true +} + +/** + * 嗅探视频链接(带配置检查) + * @param {string} url - 要嗅探的页面URL + * @param {Object} customOptions - 自定义选项(可选) + * @returns {Promise} 嗅探结果 + */ +export const sniffVideoWithConfig = async (url, customOptions = {}) => { + // 获取配置 + const config = getSnifferConfig() + + if (!config.enabled) { + throw new Error('嗅探功能未启用') + } + + if (!config.url) { + throw new Error('嗅探器接口地址未配置') + } + + // 合并配置和自定义选项 + const options = { + snifferUrl: config.url, + timeout: config.timeout, + ...customOptions + } + + return await sniffVideo(url, options) +} + +export default { + sniffVideo, + sniffVideoWithConfig, + getSnifferConfig, + isSnifferEnabled +} \ No newline at end of file diff --git a/dashboard/src/api/services/updateLogService.js b/dashboard/src/api/services/updateLogService.js new file mode 100644 index 0000000..acb7806 --- /dev/null +++ b/dashboard/src/api/services/updateLogService.js @@ -0,0 +1,356 @@ +/** + * 更新日志服务 + * 管理系统更新日志和版本信息 + */ + +// 更新日志数据 +const UPDATE_LOGS = [ + { + id: 'v2.1.0', + version: 'v2.1.0', + date: '2024-01-15', + title: '主页看板功能上线', + type: 'feature', + description: '全新的数据看板界面,提供观看统计、更新日志和推荐功能', + changes: [ + '新增观看统计图表,支持今日/昨日对比', + '新增更新日志时间线展示', + '新增猜你喜欢推荐模块', + '优化主页布局,支持固定头部滚动内容', + '集成ECharts图表库' + ], + author: 'DrPlayer Team', + importance: 'major' + }, + { + id: 'v2.0.5', + version: 'v2.0.5', + date: '2024-01-12', + title: '视频详情页优化', + type: 'improvement', + description: '优化视频详情页的用户体验和界面交互', + changes: [ + '修复下拉选择框滚动跟随问题', + '优化选择框文本显示宽度', + '改进选择框定位策略', + '增强响应式布局适配' + ], + author: 'UI Team', + importance: 'minor' + }, + { + id: 'v2.0.4', + version: 'v2.0.4', + date: '2024-01-10', + title: '播放器功能增强', + type: 'feature', + description: '播放器新增多项实用功能,提升观看体验', + changes: [ + '新增播放速度调节功能', + '支持自定义快进/快退时间', + '新增画质切换选项', + '优化全屏播放体验', + '修复音量控制问题' + ], + author: 'Player Team', + importance: 'major' + }, + { + id: 'v2.0.3', + version: 'v2.0.3', + date: '2024-01-08', + title: '性能优化', + type: 'optimization', + description: '全面优化系统性能,提升加载速度', + changes: [ + '优化视频列表加载性能', + '减少首屏加载时间', + '优化图片懒加载策略', + '压缩静态资源大小', + '改进缓存策略' + ], + author: 'Performance Team', + importance: 'minor' + }, + { + id: 'v2.0.2', + version: 'v2.0.2', + date: '2024-01-05', + title: '安全性更新', + type: 'security', + description: '重要安全更新,修复多个安全漏洞', + changes: [ + '修复XSS安全漏洞', + '加强用户输入验证', + '更新依赖包到安全版本', + '改进API接口安全性', + '增强数据传输加密' + ], + author: 'Security Team', + importance: 'critical' + }, + { + id: 'v2.0.1', + version: 'v2.0.1', + date: '2024-01-03', + title: 'Bug修复', + type: 'bugfix', + description: '修复用户反馈的多个问题', + changes: [ + '修复视频无法播放的问题', + '解决搜索功能异常', + '修复移动端适配问题', + '解决内存泄漏问题', + '修复数据同步异常' + ], + author: 'Bug Fix Team', + importance: 'minor' + }, + { + id: 'v2.0.0', + version: 'v2.0.0', + date: '2024-01-01', + title: 'DrPlayer 2.0 正式发布', + type: 'release', + description: '全新的DrPlayer 2.0版本正式发布,带来全新的用户体验', + changes: [ + '全新的UI设计语言', + '重构的播放器内核', + '支持更多视频格式', + '新增用户个人中心', + '支持多设备同步', + '新增离线下载功能', + '优化搜索算法', + '支持弹幕功能' + ], + author: 'DrPlayer Team', + importance: 'major' + } +] + +/** + * 获取更新日志类型配置 + */ +export const getUpdateTypeConfig = () => { + return { + feature: { + label: '新功能', + color: '#00b42a', + icon: '🚀' + }, + improvement: { + label: '功能优化', + color: '#165dff', + icon: '⚡' + }, + optimization: { + label: '性能优化', + color: '#ff7d00', + icon: '🔧' + }, + security: { + label: '安全更新', + color: '#f53f3f', + icon: '🔒' + }, + bugfix: { + label: 'Bug修复', + color: '#722ed1', + icon: '🐛' + }, + release: { + label: '版本发布', + color: '#f7ba1e', + icon: '🎉' + } + } +} + +/** + * 获取重要性配置 + */ +export const getImportanceConfig = () => { + return { + critical: { + label: '紧急', + color: '#f53f3f', + priority: 4 + }, + major: { + label: '重要', + color: '#ff7d00', + priority: 3 + }, + minor: { + label: '一般', + color: '#165dff', + priority: 2 + }, + trivial: { + label: '轻微', + color: '#86909c', + priority: 1 + } + } +} + +/** + * 获取所有更新日志 + */ +export const getAllUpdateLogs = () => { + return UPDATE_LOGS.sort((a, b) => new Date(b.date) - new Date(a.date)) +} + +/** + * 根据类型筛选更新日志 + */ +export const getUpdateLogsByType = (type) => { + return UPDATE_LOGS + .filter(log => log.type === type) + .sort((a, b) => new Date(b.date) - new Date(a.date)) +} + +/** + * 根据重要性筛选更新日志 + */ +export const getUpdateLogsByImportance = (importance) => { + return UPDATE_LOGS + .filter(log => log.importance === importance) + .sort((a, b) => new Date(b.date) - new Date(a.date)) +} + +/** + * 获取最近的更新日志 + */ +export const getRecentUpdateLogs = (limit = 5) => { + return UPDATE_LOGS + .sort((a, b) => new Date(b.date) - new Date(a.date)) + .slice(0, limit) +} + +/** + * 根据日期范围获取更新日志 + */ +export const getUpdateLogsByDateRange = (startDate, endDate) => { + const start = new Date(startDate) + const end = new Date(endDate) + + return UPDATE_LOGS + .filter(log => { + const logDate = new Date(log.date) + return logDate >= start && logDate <= end + }) + .sort((a, b) => new Date(b.date) - new Date(a.date)) +} + +/** + * 搜索更新日志 + */ +export const searchUpdateLogs = (keyword) => { + const lowerKeyword = keyword.toLowerCase() + + return UPDATE_LOGS + .filter(log => { + return ( + log.title.toLowerCase().includes(lowerKeyword) || + log.description.toLowerCase().includes(lowerKeyword) || + log.version.toLowerCase().includes(lowerKeyword) || + log.changes.some(change => change.toLowerCase().includes(lowerKeyword)) + ) + }) + .sort((a, b) => new Date(b.date) - new Date(a.date)) +} + +/** + * 获取更新统计信息 + */ +export const getUpdateStats = () => { + const typeConfig = getUpdateTypeConfig() + const importanceConfig = getImportanceConfig() + + // 按类型统计 + const typeStats = {} + Object.keys(typeConfig).forEach(type => { + typeStats[type] = UPDATE_LOGS.filter(log => log.type === type).length + }) + + // 按重要性统计 + const importanceStats = {} + Object.keys(importanceConfig).forEach(importance => { + importanceStats[importance] = UPDATE_LOGS.filter(log => log.importance === importance).length + }) + + // 按月份统计 + const monthlyStats = {} + UPDATE_LOGS.forEach(log => { + const month = log.date.substring(0, 7) // YYYY-MM + monthlyStats[month] = (monthlyStats[month] || 0) + 1 + }) + + return { + total: UPDATE_LOGS.length, + byType: typeStats, + byImportance: importanceStats, + byMonth: monthlyStats, + latestVersion: UPDATE_LOGS[0]?.version || 'v1.0.0', + latestDate: UPDATE_LOGS[0]?.date || new Date().toISOString().split('T')[0] + } +} + +/** + * 格式化日期显示 + */ +export const formatDate = (dateString) => { + const date = new Date(dateString) + const now = new Date() + const diffTime = Math.abs(now - date) + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + + if (diffDays === 1) { + return '昨天' + } else if (diffDays <= 7) { + return `${diffDays}天前` + } else if (diffDays <= 30) { + const weeks = Math.floor(diffDays / 7) + return `${weeks}周前` + } else { + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: 'long', + day: 'numeric' + }) + } +} + +/** + * 获取版本比较结果 + */ +export const compareVersions = (version1, version2) => { + const v1Parts = version1.replace('v', '').split('.').map(Number) + const v2Parts = version2.replace('v', '').split('.').map(Number) + + for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { + const v1Part = v1Parts[i] || 0 + const v2Part = v2Parts[i] || 0 + + if (v1Part > v2Part) return 1 + if (v1Part < v2Part) return -1 + } + + return 0 +} + +// 默认导出服务对象 +export default { + getAllUpdateLogs, + getUpdateLogsByType, + getUpdateLogsByImportance, + getRecentUpdateLogs, + getUpdateLogsByDateRange, + searchUpdateLogs, + getUpdateStats, + getUpdateTypeConfig, + getImportanceConfig, + formatDate, + compareVersions +} \ No newline at end of file diff --git a/dashboard/src/api/services/video.js b/dashboard/src/api/services/video.js new file mode 100644 index 0000000..1265b39 --- /dev/null +++ b/dashboard/src/api/services/video.js @@ -0,0 +1,622 @@ +/** + * 视频相关业务接口服务 + * 封装视频播放、详情、搜索等功能 + */ + +import { + getHomeData, + getCategoryData, + getVideoDetail, + getPlayData, + parsePlayUrl, + searchVideos, + refreshModule, + executeAction +} from '../modules/module' +import { parseVideo } from '../modules/parse' +import { encodeFilters, validateModule, validateVideoId } from '../utils' +import { processExtendParam } from '@/utils/apiUtils' +import { + createVideoInfo, + createSearchParams, + createPaginationInfo, + VIDEO_TYPES, + SORT_TYPES +} from '../types' + + + +/** + * 视频服务类 + */ +class VideoService { + constructor() { + this.cache = new Map() + this.cacheTimeout = 5 * 60 * 1000 // 5分钟缓存 + } + + /** + * 获取首页推荐视频 + * @param {string} module - 模块名称 + * @param {object} options - 选项参数 + * @param {string} options.apiUrl - API地址 + * @returns {Promise} 首页数据 + */ + async getRecommendVideos(module, options = {}) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + const { apiUrl, ...otherOptions } = options + const cacheKey = `home_${module}_${JSON.stringify(options)}` + console.log('[VideoService] getRecommendVideos 缓存检查:', { + module, + cacheKey, + cacheSize: this.cache.size, + allCacheKeys: Array.from(this.cache.keys()) + }); + + const cached = this.getFromCache(cacheKey) + if (cached) { + console.log('[VideoService] 使用缓存数据:', { + module, + videosCount: cached.videos?.length || 0, + categoriesCount: cached.categories?.length || 0 + }); + return cached + } + + console.log('[VideoService] 缓存未命中,发起新请求:', module); + + try { + const requestOptions = { ...otherOptions } + if (apiUrl) { + requestOptions.apiUrl = apiUrl + } + const response = await getHomeData(module, requestOptions) + + // 格式化返回数据 + const result = { + categories: response.class || [], + filters: response.filters || {}, + videos: (response.list || []).map(this.formatVideoInfo), + pagination: this.createPagination(response) + } + + console.log('[VideoService] 新数据已获取并缓存:', { + module, + videosCount: result.videos?.length || 0, + categoriesCount: result.categories?.length || 0, + cacheKey + }); + + this.setCache(cacheKey, result) + return result + } catch (error) { + console.error('获取首页推荐视频失败:', error) + throw error + } + } + + /** + * 获取分类视频列表 + * @param {string} module - 模块名称 + * @param {object} params - 分类参数 + * @param {string} params.typeId - 分类ID + * @param {number} params.page - 页码 + * @param {object} params.filters - 筛选条件 + * @returns {Promise} 分类视频数据 + */ + async getCategoryVideos(module, params) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + const { typeId, page = 1, filters = {}, apiUrl, extend } = params + + if (!typeId) { + throw new Error('分类ID不能为空') + } + + const cacheKey = `category_${module}_${typeId}_${page}_${JSON.stringify(filters)}` + const cached = this.getFromCache(cacheKey) + if (cached) { + return cached + } + + try { + const requestParams = { + t: typeId, + pg: page + } + + // 编码筛选条件 + if (Object.keys(filters).length > 0) { + requestParams.ext = encodeFilters(filters) + } + + // 添加extend参数 + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 添加apiUrl参数 + if (apiUrl) { + requestParams.apiUrl = apiUrl + } + + const response = await getCategoryData(module, requestParams) + + // 格式化返回数据 + const result = { + videos: (response.list || []).map(this.formatVideoInfo), + pagination: this.createPagination(response, page), + filters: response.filters || {}, + total: response.total || 0 + } + + this.setCache(cacheKey, result) + return result + } catch (error) { + console.error('获取分类视频失败:', error) + throw error + } + } + + /** + * 获取视频详情 + * @param {string} module - 模块名称 + * @param {string} videoId - 视频ID + * @param {string} apiUrl - 站点API地址 + * @param {boolean} skipCache - 是否跳过缓存 + * @param {string} extend - 扩展参数 + * @returns {Promise} 视频详情数据 + */ + async getVideoDetails(module, videoId, apiUrl, skipCache = false, extend = null) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + if (!validateVideoId(videoId)) { + throw new Error('无效的视频ID') + } + + const cacheKey = `detail_${module}_${videoId}` + + // 如果不跳过缓存,则检查缓存 + if (!skipCache) { + const cached = this.getFromCache(cacheKey) + if (cached) { + console.log('使用缓存的视频详情:', { module, videoId }) + return cached + } + } else { + console.log('跳过缓存,强制获取最新视频详情:', { module, videoId }) + } + + try { + const params = { ids: videoId } + if (apiUrl) { + params.apiUrl = apiUrl + } + const processedExtend = processExtendParam(extend) + if (processedExtend) { + params.extend = processedExtend + } + const response = await getVideoDetail(module, params) + + if (!response.list || response.list.length === 0) { + throw new Error('视频不存在') + } + + const videoInfo = this.formatVideoInfo(response.list[0]) + + // 解析播放地址 + if (videoInfo.vod_play_url) { + videoInfo.playList = this.parsePlayUrls(videoInfo.vod_play_url, videoInfo.vod_play_from) + } + + this.setCache(cacheKey, videoInfo) + return videoInfo + } catch (error) { + console.error('获取视频详情失败:', error) + throw error + } + } + + /** + * 搜索视频 + * @param {string} module - 模块名称 + * @param {object} params - 搜索参数 + * @param {string} params.keyword - 搜索关键词 + * @param {number} params.page - 页码 + * @param {string} params.extend - 扩展参数 + * @param {string} params.apiUrl - 站点API地址 + * @returns {Promise} 搜索结果 + */ + async searchVideo(module, params) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + const { keyword, page = 1, extend, apiUrl } = params + + if (!keyword || keyword.trim().length === 0) { + throw new Error('搜索关键词不能为空') + } + + try { + const requestParams = { + wd: keyword.trim(), + pg: page + } + + // 添加extend参数 + const processedExtend = processExtendParam(extend) + if (processedExtend) { + requestParams.extend = processedExtend + } + + // 添加apiUrl参数 + if (apiUrl) { + requestParams.apiUrl = apiUrl + } + + const response = await searchVideos(module, requestParams) + + // 格式化返回数据 + const result = { + videos: (response.list || []).map(this.formatVideoInfo), + pagination: this.createPagination(response, page), + keyword: keyword.trim(), + total: response.total || 0, + rawResponse: response // 添加原始响应数据用于调试 + } + + return result + } catch (error) { + console.error('搜索视频失败:', error) + throw error + } + } + + /** + * 获取播放地址 + * @param {string} module - 模块名称 + * @param {string} playUrl - 播放地址 + * @param {string} apiUrl - API地址 + * @param {string} extend - 扩展参数 + * @returns {Promise} 播放数据 + */ + async getPlayUrl(module, playUrl, apiUrl, extend = null) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + if (!playUrl) { + throw new Error('播放地址不能为空') + } + + try { + const params = { play: playUrl } + if (apiUrl) { + params.apiUrl = apiUrl + } + const processedExtend = processExtendParam(extend) + if (processedExtend) { + params.extend = processedExtend + } + const response = await getPlayData(module, params) + + return { + url: response.url || playUrl, + headers: response.headers || {}, + parse: response.parse || false, + jx: response.jx || '' + } + } catch (error) { + console.error('获取播放地址失败:', error) + throw error + } + } + + /** + * 解析视频地址 + * @param {string} jx - 解析器名称 + * @param {string} url - 视频地址 + * @param {object} options - 解析选项 + * @returns {Promise} 解析结果 + */ + async parseVideoUrl(jx, url, options = {}) { + if (!jx) { + throw new Error('解析器名称不能为空') + } + + if (!url) { + throw new Error('视频地址不能为空') + } + + try { + const response = await parseVideo(jx, { url, ...options }) + + return { + url: response.url || url, + type: response.type || 'mp4', + headers: response.headers || {}, + success: response.success !== false + } + } catch (error) { + console.error('解析视频地址失败:', error) + throw error + } + } + + /** + * 解析选集播放地址 - T4接口专用 + * @param {string} module - 模块名称 + * @param {object} params - 播放参数 + * @param {string} params.play - 播放地址或ID(选集链接) + * @param {string} params.flag - 源标识(线路名称) + * @param {string} params.apiUrl - API地址 + * @param {string} params.extend - 扩展参数 + * @returns {Promise} 播放解析结果 + */ + async parseEpisodePlayUrl(module, params) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + const { play, flag, apiUrl, extend } = params + + if (!play) { + throw new Error('播放地址不能为空') + } + + try { + console.log('VideoService: 开始解析选集播放地址', { module, params }) + + const parseParams = { play, extend: processExtendParam(extend) } + + // 添加flag参数(线路名称) + if (flag) { + parseParams.flag = flag + } + + // 添加API地址 + if (apiUrl) { + parseParams.apiUrl = apiUrl + } + + const result = await parsePlayUrl(module, parseParams) + console.log('VideoService: 选集播放解析结果', result) + + return result + } catch (error) { + console.error('VideoService: 解析选集播放地址失败:', error) + throw error + } + } + + /** + * 执行T4 Action动作 + * @param {string} module - 模块名称 + * @param {string} actionName - 动作名称 + * @param {object} options - 选项参数 + * @param {string} options.apiUrl - API地址 + * @param {string} options.extend - 扩展参数 + * @returns {Promise} Action执行结果 + */ + async executeT4Action(module, actionName, options = {}) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + if (!actionName || actionName.trim().length === 0) { + throw new Error('动作名称不能为空') + } + + try { + const actionData = { + action: actionName.trim(), + value: options.value || '', + extend: processExtendParam(options.extend), + apiUrl: options.apiUrl + } + + console.log('执行T4 action:', { module, actionData }) + + const result = await executeAction(module, actionData) + console.log('T4 action执行结果:', result) + + return result + } catch (error) { + console.error('T4 action执行失败:', error) + throw error + } + } + + /** + * 刷新模块数据 + * @param {string} module - 模块名称 + * @param {string} extend - 扩展参数 + * @param {string} apiUrl - API地址 + * @returns {Promise} 刷新结果 + */ + async refreshModuleData(module, extend = null, apiUrl = null) { + if (!validateModule(module)) { + throw new Error('无效的模块名称') + } + + try { + // 清除相关缓存 + this.clearModuleCache(module) + + const processedExtend = processExtendParam(extend) + const response = await refreshModule(module, processedExtend, apiUrl) + + return { + success: true, + message: response.msg || '刷新成功', + lastUpdate: response.data?.lastUpdate || new Date().toISOString() + } + } catch (error) { + console.error('刷新模块数据失败:', error) + throw error + } + } + + /** + * 格式化视频信息 + * @param {object} rawVideo - 原始视频数据 + * @returns {object} 格式化后的视频信息 + */ + formatVideoInfo(rawVideo) { + const video = createVideoInfo() + + Object.keys(video).forEach(key => { + if (rawVideo[key] !== undefined) { + video[key] = rawVideo[key] + } + }) + + // 处理特殊字段 + if (rawVideo.vod_hits) { + video.vod_hits = parseInt(rawVideo.vod_hits) || 0 + } + + if (rawVideo.vod_score) { + video.vod_score = parseFloat(rawVideo.vod_score) || 0 + } + + return video + } + + /** + * 解析播放地址列表 + * @param {string} playUrls - 播放地址字符串 + * @param {string} playFrom - 播放来源字符串 + * @returns {Array} 播放地址列表 + */ + parsePlayUrls(playUrls, playFrom) { + if (!playUrls) return [] + + const fromList = playFrom ? playFrom.split('$$$') : ['默认'] + const urlList = playUrls.split('$$$') + + return fromList.map((from, index) => ({ + from: from.trim(), + urls: this.parseEpisodeUrls(urlList[index] || ''), + index + })) + } + + /** + * 解析剧集地址 + * @param {string} episodeUrls - 剧集地址字符串 + * @returns {Array} 剧集地址列表 + */ + parseEpisodeUrls(episodeUrls) { + if (!episodeUrls) return [] + + return episodeUrls.split('#').map((episode, index) => { + const [name, url] = episode.split('$') + return { + name: name || `第${index + 1}集`, + url: url || '', + index + } + }).filter(episode => episode.url) + } + + /** + * 创建分页信息 + * @param {object} response - 响应数据 + * @param {number} currentPage - 当前页码 + * @returns {object} 分页信息 + */ + createPagination(response, currentPage = 1) { + const pagination = createPaginationInfo() + + pagination.page = currentPage + + // 处理不同的API响应格式 + const total = response.total || response.recordcount || 0 + const pageCount = response.pagecount || response.totalPages || 0 + const pageSize = response.limit || response.pagesize || 20 + const currentList = response.list || [] + + pagination.total = total + pagination.pageSize = pageSize + + // 如果API返回了总页数,直接使用 + if (pageCount > 0) { + pagination.totalPages = pageCount + pagination.hasNext = currentPage < pageCount + } else if (total > 0) { + // 否则根据总数计算 + pagination.totalPages = Math.ceil(total / pageSize) + pagination.hasNext = currentPage < pagination.totalPages + } else { + // 如果没有总数信息,根据当前返回的数据判断 + // 检查是否有"no_data"标识 + const hasNoDataFlag = currentList.some(item => + item.vod_id === 'no_data' || + item.vod_name === 'no_data' || + (typeof item === 'string' && item.includes('no_data')) + ) + + if (hasNoDataFlag || currentList.length === 0) { + // 如果有no_data标识或列表为空,表示没有更多数据 + pagination.hasNext = false + pagination.totalPages = currentPage + } else { + // 否则假设还有下一页,需要实际请求下一页来确认 + pagination.hasNext = true + pagination.totalPages = currentPage + 1 + } + } + + pagination.hasPrev = currentPage > 1 + + return pagination + } + + /** + * 缓存操作 + */ + getFromCache(key) { + const cached = this.cache.get(key) + if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { + return cached.data + } + this.cache.delete(key) + return null + } + + setCache(key, data) { + this.cache.set(key, { + data, + timestamp: Date.now() + }) + } + + clearModuleCache(module) { + for (const key of this.cache.keys()) { + if (key.includes(module)) { + this.cache.delete(key) + } + } + } + + clearAllCache() { + this.cache.clear() + } +} + +// 创建单例实例 +const videoService = new VideoService() + + + +export default videoService \ No newline at end of file diff --git a/dashboard/src/api/services/watchStatsService.js b/dashboard/src/api/services/watchStatsService.js new file mode 100644 index 0000000..61ed957 --- /dev/null +++ b/dashboard/src/api/services/watchStatsService.js @@ -0,0 +1,318 @@ +/** + * 观看统计数据服务 + * 管理用户观看历史和统计数据 + */ + +// 本地存储键名 +const STORAGE_KEYS = { + WATCH_HISTORY: 'drplayer_watch_history', + DAILY_STATS: 'drplayer_daily_stats', + WEEKLY_STATS: 'drplayer_weekly_stats' +} + +/** + * 获取今日日期字符串 + */ +const getTodayString = () => { + return new Date().toISOString().split('T')[0] +} + +/** + * 获取昨日日期字符串 + */ +const getYesterdayString = () => { + const yesterday = new Date() + yesterday.setDate(yesterday.getDate() - 1) + return yesterday.toISOString().split('T')[0] +} + +/** + * 获取本周日期范围 + */ +const getWeekRange = () => { + const today = new Date() + const dayOfWeek = today.getDay() + const startOfWeek = new Date(today) + startOfWeek.setDate(today.getDate() - dayOfWeek) + + const weekDates = [] + for (let i = 0; i < 7; i++) { + const date = new Date(startOfWeek) + date.setDate(startOfWeek.getDate() + i) + weekDates.push(date.toISOString().split('T')[0]) + } + + return weekDates +} + +/** + * 从本地存储获取数据 + */ +const getStorageData = (key, defaultValue = {}) => { + try { + const data = localStorage.getItem(key) + return data ? JSON.parse(data) : defaultValue + } catch (error) { + console.error('获取存储数据失败:', error) + return defaultValue + } +} + +/** + * 保存数据到本地存储 + */ +const setStorageData = (key, data) => { + try { + localStorage.setItem(key, JSON.stringify(data)) + } catch (error) { + console.error('保存存储数据失败:', error) + } +} + +/** + * 记录观看行为 + */ +export const recordWatchActivity = (videoInfo) => { + const today = getTodayString() + const timestamp = new Date().toISOString() + + // 获取观看历史 + const watchHistory = getStorageData(STORAGE_KEYS.WATCH_HISTORY, []) + + // 添加新的观看记录 + const newRecord = { + id: Date.now(), + videoId: videoInfo.id, + videoTitle: videoInfo.title, + episode: videoInfo.episode || 1, + duration: videoInfo.duration || 0, + watchTime: videoInfo.watchTime || 0, + date: today, + timestamp: timestamp + } + + watchHistory.push(newRecord) + + // 保持最近1000条记录 + if (watchHistory.length > 1000) { + watchHistory.splice(0, watchHistory.length - 1000) + } + + setStorageData(STORAGE_KEYS.WATCH_HISTORY, watchHistory) + + // 更新每日统计 + updateDailyStats(today) + + return newRecord +} + +/** + * 更新每日统计 + */ +const updateDailyStats = (date) => { + const dailyStats = getStorageData(STORAGE_KEYS.DAILY_STATS, {}) + const watchHistory = getStorageData(STORAGE_KEYS.WATCH_HISTORY, []) + + // 计算指定日期的观看集数 + const dayWatchCount = watchHistory.filter(record => record.date === date).length + + dailyStats[date] = { + date: date, + watchCount: dayWatchCount, + totalWatchTime: watchHistory + .filter(record => record.date === date) + .reduce((total, record) => total + (record.watchTime || 0), 0), + updatedAt: new Date().toISOString() + } + + setStorageData(STORAGE_KEYS.DAILY_STATS, dailyStats) +} + +/** + * 获取今日观看统计 + */ +export const getTodayStats = () => { + const today = getTodayString() + const dailyStats = getStorageData(STORAGE_KEYS.DAILY_STATS, {}) + + if (!dailyStats[today]) { + updateDailyStats(today) + return dailyStats[today] || { date: today, watchCount: 0, totalWatchTime: 0 } + } + + return dailyStats[today] +} + +/** + * 获取昨日观看统计 + */ +export const getYesterdayStats = () => { + const yesterday = getYesterdayString() + const dailyStats = getStorageData(STORAGE_KEYS.DAILY_STATS, {}) + + if (!dailyStats[yesterday]) { + updateDailyStats(yesterday) + return dailyStats[yesterday] || { date: yesterday, watchCount: 0, totalWatchTime: 0 } + } + + return dailyStats[yesterday] +} + +/** + * 获取本周观看统计 + */ +export const getWeekStats = () => { + const weekDates = getWeekRange() + const dailyStats = getStorageData(STORAGE_KEYS.DAILY_STATS, {}) + + const weekStats = weekDates.map((date, index) => { + const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] + const dayStats = dailyStats[date] || { date, watchCount: 0, totalWatchTime: 0 } + + return { + day: dayNames[index], + date: date, + count: dayStats.watchCount, + totalWatchTime: dayStats.totalWatchTime + } + }) + + return weekStats +} + +/** + * 计算同比增长率 + */ +export const calculateGrowthRate = () => { + const todayStats = getTodayStats() + const yesterdayStats = getYesterdayStats() + + if (yesterdayStats.watchCount === 0) { + return todayStats.watchCount > 0 ? 100 : 0 + } + + const growth = ((todayStats.watchCount - yesterdayStats.watchCount) / yesterdayStats.watchCount) * 100 + return Math.round(growth) +} + +/** + * 获取观看历史 + */ +export const getWatchHistory = (limit = 50) => { + const watchHistory = getStorageData(STORAGE_KEYS.WATCH_HISTORY, []) + return watchHistory + .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)) + .slice(0, limit) +} + +/** + * 获取热门观看内容 + */ +export const getPopularContent = (limit = 10) => { + const watchHistory = getStorageData(STORAGE_KEYS.WATCH_HISTORY, []) + + // 统计每个视频的观看次数 + const contentStats = {} + watchHistory.forEach(record => { + const key = record.videoId + if (!contentStats[key]) { + contentStats[key] = { + videoId: record.videoId, + videoTitle: record.videoTitle, + watchCount: 0, + lastWatched: record.timestamp + } + } + contentStats[key].watchCount++ + if (new Date(record.timestamp) > new Date(contentStats[key].lastWatched)) { + contentStats[key].lastWatched = record.timestamp + } + }) + + // 按观看次数排序 + return Object.values(contentStats) + .sort((a, b) => b.watchCount - a.watchCount) + .slice(0, limit) +} + +/** + * 清理过期数据 + */ +export const cleanupOldData = (daysToKeep = 30) => { + const cutoffDate = new Date() + cutoffDate.setDate(cutoffDate.getDate() - daysToKeep) + const cutoffString = cutoffDate.toISOString().split('T')[0] + + // 清理观看历史 + const watchHistory = getStorageData(STORAGE_KEYS.WATCH_HISTORY, []) + const filteredHistory = watchHistory.filter(record => record.date >= cutoffString) + setStorageData(STORAGE_KEYS.WATCH_HISTORY, filteredHistory) + + // 清理每日统计 + const dailyStats = getStorageData(STORAGE_KEYS.DAILY_STATS, {}) + const filteredStats = {} + Object.keys(dailyStats).forEach(date => { + if (date >= cutoffString) { + filteredStats[date] = dailyStats[date] + } + }) + setStorageData(STORAGE_KEYS.DAILY_STATS, filteredStats) + + console.log(`清理了 ${daysToKeep} 天前的数据`) +} + +/** + * 初始化模拟数据(用于演示) + */ +export const initMockData = () => { + const mockVideos = [ + { id: 'video_1', title: '斗罗大陆', episode: 1 }, + { id: 'video_2', title: '庆余年', episode: 2 }, + { id: 'video_3', title: '流浪地球2', episode: 1 }, + { id: 'video_4', title: '鬼灭之刃', episode: 5 }, + { id: 'video_5', title: '三体', episode: 3 } + ] + + // 生成过去7天的模拟数据 + for (let i = 6; i >= 0; i--) { + const date = new Date() + date.setDate(date.getDate() - i) + + // 每天随机观看1-5个视频 + const dailyWatchCount = Math.floor(Math.random() * 5) + 1 + + for (let j = 0; j < dailyWatchCount; j++) { + const randomVideo = mockVideos[Math.floor(Math.random() * mockVideos.length)] + const mockRecord = { + ...randomVideo, + duration: Math.floor(Math.random() * 3600) + 1200, // 20-80分钟 + watchTime: Math.floor(Math.random() * 3000) + 600 // 10-60分钟 + } + + // 设置特定日期的时间戳 + const timestamp = new Date(date) + timestamp.setHours(Math.floor(Math.random() * 24)) + timestamp.setMinutes(Math.floor(Math.random() * 60)) + + recordWatchActivity({ + ...mockRecord, + timestamp: timestamp.toISOString() + }) + } + } + + console.log('模拟数据初始化完成') +} + +// 默认导出服务对象 +export default { + recordWatchActivity, + getTodayStats, + getYesterdayStats, + getWeekStats, + calculateGrowthRate, + getWatchHistory, + getPopularContent, + cleanupOldData, + initMockData +} \ No newline at end of file diff --git a/dashboard/src/api/types/index.js b/dashboard/src/api/types/index.js new file mode 100644 index 0000000..4b439d2 --- /dev/null +++ b/dashboard/src/api/types/index.js @@ -0,0 +1,219 @@ +/** + * API类型定义 + * 定义接口相关的数据结构、枚举和常量 + */ + +// 视频类型枚举 +export const VIDEO_TYPES = { + MOVIE: 'movie', // 电影 + TV: 'tv', // 电视剧 + VARIETY: 'variety', // 综艺 + CARTOON: 'cartoon', // 动漫 + CHILD: 'child', // 少儿 + DOCUMENTARY: 'doco', // 纪录片 + CHOICE: 'choice' // 精选 +} + +// 视频状态枚举 +export const VIDEO_STATUS = { + NORMAL: 'normal', // 正常 + UPDATING: 'updating', // 更新中 + COMPLETED: 'completed', // 已完结 + PREVIEW: 'preview' // 预告 +} + +// 排序方式枚举 +export const SORT_TYPES = { + TIME: 'time', // 按时间排序 + NAME: 'name', // 按名称排序 + HITS: 'hits', // 按点击量排序 + SCORE: 'score' // 按评分排序 +} + +// 排序顺序枚举 +export const SORT_ORDER = { + ASC: 'asc', // 升序 + DESC: 'desc' // 降序 +} + +// 地区枚举 +export const REGIONS = { + MAINLAND: 'mainland', // 大陆 + HONGKONG: 'hongkong', // 香港 + TAIWAN: 'taiwan', // 台湾 + KOREA: 'korea', // 韩国 + JAPAN: 'japan', // 日本 + USA: 'usa', // 美国 + UK: 'uk', // 英国 + FRANCE: 'france', // 法国 + GERMANY: 'germany', // 德国 + OTHER: 'other' // 其他 +} + +// 年份范围 +export const YEAR_RANGES = { + RECENT: '2020-2024', // 近期 + CLASSIC: '2010-2019', // 经典 + OLD: '2000-2009', // 怀旧 + ANCIENT: '1990-1999' // 古典 +} + +// 请求状态枚举 +export const REQUEST_STATUS = { + IDLE: 'idle', // 空闲 + LOADING: 'loading', // 加载中 + SUCCESS: 'success', // 成功 + ERROR: 'error' // 错误 +} + +// 缓存策略枚举 +export const CACHE_STRATEGY = { + NO_CACHE: 'no-cache', // 不缓存 + CACHE_FIRST: 'cache-first', // 缓存优先 + NETWORK_FIRST: 'network-first', // 网络优先 + CACHE_ONLY: 'cache-only' // 仅缓存 +} + +/** + * 视频信息数据结构 + */ +export const createVideoInfo = () => ({ + vod_id: '', // 视频ID + vod_name: '', // 视频名称 + vod_pic: '', // 视频封面 + vod_remarks: '', // 视频备注 + vod_content: '', // 视频简介 + vod_play_from: '', // 播放来源 + vod_play_url: '', // 播放地址 + vod_time: '', // 更新时间 + vod_year: '', // 年份 + vod_area: '', // 地区 + vod_lang: '', // 语言 + vod_actor: '', // 演员 + vod_director: '', // 导演 + vod_writer: '', // 编剧 + vod_blurb: '', // 简介 + vod_class: '', // 分类 + vod_tag: '', // 标签 + vod_score: '', // 评分 + vod_hits: 0, // 点击量 + vod_duration: '', // 时长 + vod_total: 0, // 总集数 + vod_serial: 0, // 当前集数 + vod_tv: '', // 电视台 + vod_weekday: '', // 播出时间 + vod_status: VIDEO_STATUS.NORMAL +}) + +/** + * 分类信息数据结构 + */ +export const createCategoryInfo = () => ({ + type_id: '', // 分类ID + type_name: '', // 分类名称 + type_sort: 0, // 排序 + type_status: 1 // 状态 +}) + +/** + * 筛选条件数据结构 + */ +export const createFilterInfo = () => ({ + type: '', // 类型 + area: '', // 地区 + year: '', // 年份 + lang: '', // 语言 + sort: SORT_TYPES.TIME, // 排序方式 + order: SORT_ORDER.DESC // 排序顺序 +}) + +/** + * 分页信息数据结构 + */ +export const createPaginationInfo = () => ({ + page: 1, // 当前页码 + pageSize: 20, // 每页数量 + total: 0, // 总数量 + totalPages: 0, // 总页数 + hasNext: false, // 是否有下一页 + hasPrev: false // 是否有上一页 +}) + +/** + * 搜索参数数据结构 + */ +export const createSearchParams = () => ({ + keyword: '', // 搜索关键词 + type: '', // 搜索类型 + page: 1, // 页码 + pageSize: 20, // 每页数量 + filters: createFilterInfo() // 筛选条件 +}) + +/** + * API响应数据结构 + */ +export const createApiResponse = () => ({ + code: 200, // 状态码 + msg: '', // 消息 + data: null, // 数据 + timestamp: Date.now() // 时间戳 +}) + +/** + * 播放信息数据结构 + */ +export const createPlayInfo = () => ({ + url: '', // 播放地址 + type: '', // 播放类型 + headers: {}, // 请求头 + parse: false, // 是否需要解析 + jx: '' // 解析器 +}) + +/** + * 站点信息数据结构 + */ +export const createSiteInfo = () => ({ + key: '', // 站点标识 + name: '', // 站点名称 + type: 0, // 站点类型 + api: '', // API地址 + searchable: 1, // 是否可搜索 + quickSearch: 1, // 是否支持快速搜索 + filterable: 1, // 是否可筛选 + order: 0, // 排序 + ext: '', // 扩展参数 + more: null // 额外配置信息(包含actions等) +}) + +/** + * 错误信息数据结构 + */ +export const createErrorInfo = () => ({ + code: '', // 错误码 + message: '', // 错误信息 + details: null, // 错误详情 + timestamp: Date.now() // 时间戳 +}) + +// 默认导出所有类型定义 +export default { + VIDEO_TYPES, + VIDEO_STATUS, + SORT_TYPES, + SORT_ORDER, + REGIONS, + YEAR_RANGES, + REQUEST_STATUS, + CACHE_STRATEGY, + createVideoInfo, + createCategoryInfo, + createFilterInfo, + createPaginationInfo, + createSearchParams, + createApiResponse, + createPlayInfo, + createSiteInfo, + createErrorInfo +} \ No newline at end of file diff --git a/dashboard/src/api/utils/index.js b/dashboard/src/api/utils/index.js new file mode 100644 index 0000000..e35216f --- /dev/null +++ b/dashboard/src/api/utils/index.js @@ -0,0 +1,321 @@ +/** + * API工具函数 + * 提供常用的数据处理、验证和转换功能 + */ +import CryptoJS from 'crypto-js' +/** + * Base64编码(使用CryptoJS) + * @param {string} text 待编码文本 + * @returns {string} Base64编码结果 + */ +export const base64Encode = (text) => { + return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text)); +} + +/** + * Base64解码(使用CryptoJS) + * @param {string} text Base64编码文本 + * @returns {string} 解码结果 + */ +export const base64Decode = (text) => { + return CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(text)); +} + +/** + * URL安全的Base64编码(Base64URL) + * 将 + 替换为 -,将 / 替换为 _,移除填充字符 = + * @param {string} text 待编码文本 + * @returns {string} URL安全的Base64编码结果 + */ +export const base64EncodeUrl = (text) => { + const base64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text)); + return base64 + .replace(/\+/g, '-') // 将 + 替换为 - + .replace(/\//g, '_') // 将 / 替换为 _ + .replace(/=/g, ''); // 移除填充字符 = +} + +/** + * URL安全的Base64解码(Base64URL) + * 将 - 替换为 +,将 _ 替换为 /,补充填充字符 = + * @param {string} text URL安全的Base64编码文本 + * @returns {string} 解码结果 + */ +export const base64DecodeUrl = (text) => { + // 恢复标准Base64格式 + let base64 = text + .replace(/-/g, '+') // 将 - 替换为 + + .replace(/_/g, '/'); // 将 _ 替换为 / + + // 补充填充字符 = + const padding = base64.length % 4; + if (padding === 2) { + base64 += '=='; + } else if (padding === 3) { + base64 += '='; + } + + return CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(base64)); +} + +/** + * MD5哈希 + * @param {string} text 待哈希文本 + * @returns {string} MD5哈希值 + */ +export function md5(text) { + return CryptoJS.MD5(text).toString(); +} + +/** + * 将筛选条件对象转换为Base64编码的JSON字符串 + * @param {object} filters - 筛选条件对象 + * @returns {string} Base64编码的JSON字符串 + */ +export const encodeFilters = (filters) => { + if (!filters || typeof filters !== 'object') { + return '' + } + + try { + const jsonStr = JSON.stringify(filters) + return base64Encode(jsonStr) + } catch (error) { + console.error('筛选条件编码失败:', error) + return '' + } +} + +/** + * 将Base64编码的JSON字符串解码为筛选条件对象 + * @param {string} encodedFilters - Base64编码的JSON字符串 + * @returns {object} 筛选条件对象 + */ +export const decodeFilters = (encodedFilters) => { + if (!encodedFilters || typeof encodedFilters !== 'string') { + return {} + } + + try { + const jsonStr = base64Decode(encodedFilters) + return JSON.parse(jsonStr) + } catch (error) { + console.error('筛选条件解码失败:', error) + return {} + } +} + +/** + * 构建查询参数字符串 + * @param {object} params - 参数对象 + * @returns {string} 查询参数字符串 + */ +export const buildQueryString = (params) => { + if (!params || typeof params !== 'object') { + return '' + } + + const searchParams = new URLSearchParams() + + Object.keys(params).forEach(key => { + const value = params[key] + if (value !== null && value !== undefined && value !== '') { + searchParams.append(key, String(value)) + } + }) + + return searchParams.toString() +} + +/** + * 解析查询参数字符串 + * @param {string} queryString - 查询参数字符串 + * @returns {object} 参数对象 + */ +export const parseQueryString = (queryString) => { + if (!queryString || typeof queryString !== 'string') { + return {} + } + + const params = {} + const searchParams = new URLSearchParams(queryString) + + for (const [key, value] of searchParams) { + params[key] = value + } + + return params +} + +/** + * 验证模块名称 + * @param {string} module - 模块名称 + * @returns {boolean} 是否有效 + */ +export const validateModule = (module) => { + if (!module || typeof module !== 'string') { + return false + } + + // 模块名称不能为空,且长度不能超过100个字符 + // 支持字母、数字、中文、下划线、连字符、方括号、圆括号等常见字符 + if (module.trim().length === 0 || module.length > 100) { + return false + } + + // 不允许包含特殊控制字符 + const invalidChars = /[\x00-\x1F\x7F]/ + return !invalidChars.test(module) +} + +/** + * 验证视频ID + * @param {string} videoId - 视频ID + * @returns {boolean} 是否有效 + */ +export const validateVideoId = (videoId) => { + if (!videoId || typeof videoId !== 'string') { + return false + } + + const trimmedId = videoId.trim() + + // 视频ID不能为空 + if (trimmedId.length === 0) { + return false + } + + // no_data 被认为是无效的视频ID + if (trimmedId === 'no_data') { + return false + } + + // 视频ID长度限制在1024字符内 + return videoId.length <= 1024 +} + +/** + * 验证URL格式 + * @param {string} url - URL字符串 + * @returns {boolean} 是否有效 + */ +export const validateUrl = (url) => { + if (!url || typeof url !== 'string') { + return false + } + + try { + new URL(url) + return true + } catch { + return false + } +} + +/** + * 格式化错误信息 + * @param {Error|string} error - 错误对象或错误信息 + * @returns {string} 格式化后的错误信息 + */ +export const formatError = (error) => { + if (!error) { + return '未知错误' + } + + if (typeof error === 'string') { + return error + } + + if (error instanceof Error) { + return error.message || '请求失败' + } + + if (error.response && error.response.data) { + const { data } = error.response + return data.msg || data.message || '服务器错误' + } + + return '网络请求失败' +} + +/** + * 深度合并对象 + * @param {object} target - 目标对象 + * @param {object} source - 源对象 + * @returns {object} 合并后的对象 + */ +export const deepMerge = (target, source) => { + if (!target || typeof target !== 'object') { + return source + } + + if (!source || typeof source !== 'object') { + return target + } + + const result = { ...target } + + Object.keys(source).forEach(key => { + if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { + result[key] = deepMerge(result[key], source[key]) + } else { + result[key] = source[key] + } + }) + + return result +} + +/** + * 防抖函数 + * @param {Function} func - 需要防抖的函数 + * @param {number} delay - 延迟时间(毫秒) + * @returns {Function} 防抖后的函数 + */ +export const debounce = (func, delay) => { + let timeoutId + + return function (...args) { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => func.apply(this, args), delay) + } +} + +/** + * 节流函数 + * @param {Function} func - 需要节流的函数 + * @param {number} delay - 延迟时间(毫秒) + * @returns {Function} 节流后的函数 + */ +export const throttle = (func, delay) => { + let lastCall = 0 + + return function (...args) { + const now = Date.now() + + if (now - lastCall >= delay) { + lastCall = now + return func.apply(this, args) + } + } +} + +// 默认导出所有工具函数 +export default { + base64Encode, + base64Decode, + base64EncodeUrl, + base64DecodeUrl, + md5, + encodeFilters, + decodeFilters, + buildQueryString, + parseQueryString, + validateModule, + validateVideoId, + validateUrl, + formatError, + deepMerge, + debounce, + throttle +} \ No newline at end of file diff --git a/dashboard/src/assets/action.json b/dashboard/src/assets/action.json new file mode 100644 index 0000000..ff74015 --- /dev/null +++ b/dashboard/src/assets/action.json @@ -0,0 +1,56 @@ +{ + "使用帮助": "本帮助。长按则分项选择查看。\n\n系统多数功能按键和选项,短按和长按有不同的功能。\n\n[#影图根目录]:安全起见,影图的根目录为tvbox(/storage/emulated/0/tvbox),未说明情况下文件或文件夹都是相对于此目录。", + "交互动作": "影图APP与站源接口间交互的动作指令(action)。用户在APP主动发出动作请求,接口根据指令返回数据或返回构建信息输入窗口的配置JSON,具有连续交互的机制。", + "动作指令": "接口回传给APP的动作指令,按用户交互起点分为静态动作和动态动作。\n\n[#静态动作]:用户通过视频分类列表主动发起交互的起点动作。动作指令以分类列表的视频JSON数据为基础,属于静态数据。所有交互动作的起始都是静态动作。\n\n[#动态动作]:静态动作构建的信息输入窗口提交动作(action)数据后,接口如果再次需要用户输入数据,可以返回新的动作配置JSON数据,此数据是交互过程中动态生成的,属于动态动作。数据结构:\n{\n \"action\":{\n  动作指令结构...\n },\n \"toast\":\"Toast显示消息\"\n}", + "动作类型": "基础动作、单项输入、多行编辑、多项输入、增强多项输入、单项选择、多项选择、消息弹窗、专项动作等。", + "视频VOD": "动作入口的视频分类列表VOD的JSON,vod_id字段值为字符型动作指令数据(json结构的需要转为字符型),vod_tag字段值固定为action,其它字段与视频类一致。例如:\n{\n \"vod_id\":\"动作指令结构...\",\n \"vod_name\":\"显示名称\",\n \"vod_tag\":\"action\"\n}", + "接口action": "接口接收动作指令的方法action。以js代码为例:function action(action, value) {...}。传参action为动作指令,value为动作指令值。返回结果消息或新的动作指令数据(动态动作)。", + "基础动作": "简单的动作指令字符串(非JSON结构),用户点击时无信息输入窗口,直接发送指令。\n{\n \"vod_id\":\"hello world\",\n \"vod_name\":\"基础动作\",\n \"vod_tag\":\"action\"\n}", + "JSON动作": "JSON结构的动作指令。通过JSON结构数据,配置更丰富的动作指令。通过选择配置不同的字段,定义不同的动作表现。", + "actionId": "识别动作的路由ID或专项动作指令,必须。字符型。", + "type": "动作的类型。input(单项输入)/edit(单项多行编辑)/multiInput(少于5个的多项输入)/multiInputX(增强的多项输入)/menu(单项选择)/select(多项选择)/msgbox(消息弹窗)等。字符型。", + "canceledOnTouchOutside": "弹出窗口是否允许触摸窗口外时取消窗口。逻辑型。", + "title": "标题。字符型。", + "width": "宽度。整型。", + "height": "高度。整型。", + "msg": "文本消息内容。字符型。", + "htmlMsg": "msgbox类动作的简单html消息内容。字符型。", + "help": "input、multiInput、multiInputX类动作的帮助说明内容,在窗口右上角显示帮助图标,点击显示帮助说明,可支持简易的HTML内容。支持的HTML标签,b(加粗)、i(斜体)、u(下划线)、strike(删除线)、em(强调)、strong(加强强调)、p(段落)、div(分区)、br(换行)、font(颜色/大小/字体)、h1~h6(标题层级)、small(小号字体)、tt(打字机字体)、blockquote(引用块)。", + "button": "按键的数量。0-无按键,1-取消,2-确定/取消, 3-确定/取消/重置。整型。", + "imageUrl": "图片URL。字符型。", + "imageHeight": "图片高度。整型。", + "imageClickCoord": "是否检测图片的点击坐标输入。逻辑型。", + "qrcode": "生成二维码的URL。字符型。", + "qrcodeSize": "二维码的大小。整型。", + "timeout": "超时时间(秒)。超时自动关闭窗口。整型。", + "httpTimeout": "T4源的动作网络访问超时时间(秒)。", + "keep": "输入确认后,窗口是否保持。逻辑型。", + "initAction": "窗口弹出时自动发送的初始化动作指令。字符型。", + "initValue": "窗口弹出时自动发送的初始化指令值。字符型。", + "cancelAction": "按窗口的取消键时发送的取消动作指令。字符型。", + "cancelValue": "按窗口的取消键时发送的取消动作指令值。字符型。", + "tip": "单项输入的输入提示,单项输入时必须。字符型。", + "value": "单项输入的初始化值。字符型。", + "selectData": "单项输入的预定义选项,用于常见值的快速选择输入。各选项间用“,”分隔,选项值可使用“名称:=值”方式。字符型。", + "input": "多项输入的项目定义JSON数组。每个输入项目使用一个JSON对象进行定义。\n\n[#id]:项目id。\n\n[#name]:项目名称。\n\n[#tip]:项目输入提示。\n\n[#value]:项目初始值。\n\n[#selectData]:项目输入预定义选项。各选项间用“,”分隔,选项值可使用“名称:=值”方式。特殊的输入选择:[folder]-选择文件夹,[file]-选择文件,[calendar]-选择日期,[image]-选择图像文件转为BASE64。multiInputX。\n\n[#quickSelect]:是否能快速选择。单项选择时有效。quickSelect为true且inputType为0时,只输入快速选择项目不显示输入框等。multiInputX。\n\n[#onlyQuickSelect]:是否只快速选择,隐藏输入框等。单项选择时有效。multiInputX。\n\n[#selectWidth]:选择窗的宽度。multiInputX。\n\n[#multiSelect]:是否多选。multiInputX。\n\n[#selectColumn]:选择窗的列数。multiInputX。\n\n[#inputType]:项目输入类型。0-项目输入框只读,但可通过选项输入。129-密码输入。inputType为0且quickSelect为true时,只输入快速选择项目不显示输入框等。multiInputX。\n\n[#multiLine]:项目输入框的行数(多行编辑)。multiInputX。\n\n[#validation]:提交时项目输入值校验正则表达式。multiInputX。\n\n[#help]:项目输入的帮助说明,可支持简易的HTML内容。支持的HTML标签,b(加粗)、i(斜体)、u(下划线)、strike(删除线)、em(强调)、strong(加强强调)、p(段落)、div(分区)、br(换行)、font(颜色/大小/字体)、h1~h6(标题层级)、small(小号字体)、tt(打字机字体)、blockquote(引用块)。multiInputX。", + "dimAmount": "设置窗口背景暗化效果,用于调整背景的暗化程度(透明度)。其值范围为0.0到1.0。", + "bottom": "底部对齐和底边距。整型。", + "column": "单项选择或多项选择窗口的列数。整型。", + "option": "单项选择或多项选择的选项定义JSON数组。每个选项使用一个JSON对象进行定义。\n\n[#name]:选项名称。\n\n[#action]:选项动作值。\n\n[#selected]:选项默认是否已选。多项选择是可用。", + "单项输入": "type为input。要求用户输入一个字段的动作,JSON结构,部分字段根据需要选用。\n{\n actionId:'动作路由ID',\n id:'输入项目id',\n type:'input',\n width:450,\n title:'输入窗口标题',\n tip:'输入提示',\n value:'输入初始值',\n msg:'窗口文本说明',\n imageUrl:'窗口显示图片的URL',\n imageHeight:200,\n qrcode:'生成二维码的URL',\n qrcodeSize:'300',\n initAction:'initAction',\n initValue:requestId,\n button:2,\n selectData:'1:=快速输入一,2:=快速输入二,3:=快速输入三'\n}", + "多行编辑": "type为edit。要求用户在一个多行编辑区输入单个字段内容的动作,JSON结构。", + "多项输入": "type为multiInput。要求用户输入多个字段(5个以内)的动作,JSON结构。建议使用“增强多项输入”动作。", + "增强多项输入": "type为multiInputX。要求用户输入多个字段(不限制个数)的动作,JSON结构。", + "单项选择": "type为menu。要求用户在列表中选择一个项目的动作,JSON结构。", + "多项选择": "type为select。要求用户在列表中选择多个项目的动作,JSON结构。", + "消息弹窗": "type为msgbox。弹出窗口显示消息,JSON结构。", + "专项动作": "专项动作为动态动作,接口让APP执行一些特定的行为动作。actionId值为行为特定的标识。__self_search__(源内搜索)、__detail__(详情页)、__ktvplayer__(KTV播放)、__refresh_list__(刷新列表)、__copy__(复制)、__keep__(保持窗口)。", + "__self_search__": "源内搜索。\n\n[#skey]:目标源key,可选,未设置或为空则使用当前源。\n\n[#name]:搜索分类名称。\n\n[#tid]:使用分类ID传递的搜索值。\n\n[#flag]:列表视图参数。\n\n[#folder]:多个分类切换搜索的配置,设置此项则忽略name、tid和flag。folder可多项合并设置为一个字符,各项间使用“#”分隔,每项中的name、tid和flag使用“$”分隔。floder也可使用JSON数组,每项分别设置name、tid和flag。", + "__detail__": "跳转到指定站源解析详情页播放。\n\n[#skey]:目标源key。\n\n[#ids]:传递给详情页的视频ids。", + "__ktvplayer__": "跳转到KTV播放器播放指定链接。\n\n[#name]:歌名。\n\n[#id]:歌曲的直链。", + "__refresh_list__": "刷新当前分类的列表。无其它参数。", + "__copy__": "把返回的内容复制到剪贴板。content:复制的内容", + "__keep__": "保持窗口不关闭。\n\n[#msg]:更新窗口里的文本消息内容。\n\n[#reset]:窗口中的输入项目内容是否清空。", + "图片坐标示例": "获取在图片点击的位置坐标用于验证输入的js示例。\n {\n  vod_id: JSON.stringify({\n   actionId: '图片点击坐标',\n   id: 'coord',\n   type: 'input',\n   title: '图片点击坐标',\n   tip: '请输入图片中文字的坐标',\n   value: '',\n   msg: '点击图片上文字获取坐标',\n   imageUrl: 'https://pic.imgdb.cn/item/667ce9f4d9c307b7e9f9d052.webp',\n   imageHeight: 300,\n   imageClickCoord: true,\n   button: 3,\n  }),\n  vod_name: '图片点击坐标',\n  vod_pic: 'https://pic.imgdb.cn/item/667ce9f4d9c307b7e9f9d052.webp',\n  vod_tag:'action'\n }", + "多项输入示例": "多个不同类型输入项的js示例。\n {\n  vod_id: JSON.stringify({\n   actionId: '多项输入',\n   type: 'multiInputX',\n   canceledOnTouchOutside: true,\n   title: '多项输入(multiInputX)',\n   width: 716,\n   bottom: 1,\n   dimAmount: 0.3,\n   button: 3,\n   input: [\n    {\n     id: 'item1',\n     name: '文件夹路径(文件夹选择器)',\n     tip: '请输入文件夹路径',\n     value: '',\n     selectData: '[folder]',\n     inputType: 0,\n    },\n    {\n     id: 'item2',\n     name: '日期(日期选择器)',\n     tip: '请输入项目2内容',\n     value: '',\n     selectData: '[calendar]',\n     inputType: 0,\n     \n    },\n    {\n     id: 'item3',\n     name: '文件路径(文件选择器)',\n     tip: '请输入文件路径',\n     value: '',\n     selectData: '[file]',\n     inputType: 0,\n    },\n    {\n     id: 'item4',\n     name: '多项选择',\n     tip: '请输入多项内容,以“,”分隔',\n     value: '',\n     selectData: '[请选择字母]a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z',\n     selectWidth: 640,\n     multiSelect: true,\n     selectColumn: 4,\n     inputType: 0,\n    },\n    {\n     id: 'item5',\n     name: '多行输入',\n     tip: '请输入项目5内容',\n     value: '',\n     multiLine: 5,\n    },\n    {\n     id: 'item6',\n     name: '密码输入',\n     tip: '请输入项目6内容',\n     value: '',\n     inputType: 129,\n    },\n    {\n     id: 'item7',\n     name: '图像base64(图像文件选择器)',\n     tip: '请输入项目7内容',\n     value: '',\n     selectData: '[image]',\n     multiLine: 3,\n     inputType: 0,\n    },\n    {\n     id: 'item8',\n     name: '单项选择',\n     tip: '请输入项目8内容',\n     value: '',\n     selectData: '[请选择地方]a,b,c,d'\n    },\n    {\n     id: 'item9',\n     name: '单行输入并校验',\n     tip: '请输入项目9内容',\n     value: '',\n     validation: '[0-9]{6,12}',\n    }\n   ]\n  }),\n  vod_name: '多项输入',\n  vod_tag:'action'\n }" +} \ No newline at end of file diff --git a/dashboard/src/assets/icon_font/demo.css b/dashboard/src/assets/icon_font/demo.css new file mode 100644 index 0000000..a67054a --- /dev/null +++ b/dashboard/src/assets/icon_font/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: "iconfont logo"; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); +} + +.logo { + font-family: "iconfont logo"; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown>p, +.markdown>blockquote, +.markdown>.highlight, +.markdown>ol, +.markdown>ul { + width: 80%; +} + +.markdown ul>li { + list-style: circle; +} + +.markdown>ul li, +.markdown blockquote ul>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown>ul li p, +.markdown>ol li p { + margin: 0.6em 0; +} + +.markdown ol>li { + list-style: decimal; +} + +.markdown>ol li, +.markdown blockquote ol>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown>table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown>table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown>table th, +.markdown>table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown>table th { + background: #F7F7F7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown>br, +.markdown>p>br { + clear: both; +} + + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre)>code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre)>code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/dashboard/src/assets/icon_font/demo_index.html b/dashboard/src/assets/icon_font/demo_index.html new file mode 100644 index 0000000..f84605f --- /dev/null +++ b/dashboard/src/assets/icon_font/demo_index.html @@ -0,0 +1,970 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    下载
    +
    &#xfcb9;
    +
  • + +
  • + +
    夸克
    +
    &#xe67d;
    +
  • + +
  • + +
    folder
    +
    &#xe690;
    +
  • + +
  • + +
    file_blank
    +
    &#xe692;
    +
  • + +
  • + +
    file_zip
    +
    &#xe698;
    +
  • + +
  • + +
    file_excel
    +
    &#xe699;
    +
  • + +
  • + +
    file_ppt
    +
    &#xe69a;
    +
  • + +
  • + +
    file_word
    +
    &#xe69b;
    +
  • + +
  • + +
    file_pdf
    +
    &#xe69c;
    +
  • + +
  • + +
    file_music
    +
    &#xe69d;
    +
  • + +
  • + +
    file_video
    +
    &#xe69e;
    +
  • + +
  • + +
    file_img
    +
    &#xe69f;
    +
  • + +
  • + +
    file_ai
    +
    &#xe6a1;
    +
  • + +
  • + +
    file_psd
    +
    &#xe6a2;
    +
  • + +
  • + +
    file_bt
    +
    &#xe6a3;
    +
  • + +
  • + +
    file_txt
    +
    &#xe6a4;
    +
  • + +
  • + +
    file_exe
    +
    &#xe6a5;
    +
  • + +
  • + +
    file_html
    +
    &#xe6a7;
    +
  • + +
  • + +
    file_cad
    +
    &#xe6a8;
    +
  • + +
  • + +
    file_code
    +
    &#xe6a9;
    +
  • + +
  • + +
    file_flash
    +
    &#xe6aa;
    +
  • + +
  • + +
    file_iso
    +
    &#xe6ab;
    +
  • + +
  • + +
    file_cloud
    +
    &#xe6ac;
    +
  • + +
  • + +
    文件夹
    +
    &#xe80c;
    +
  • + +
  • + +
    主页
    +
    &#xe71f;
    +
  • + +
  • + +
    设置
    +
    &#xe63a;
    +
  • + +
  • + +
    视频直播
    +
    &#xe621;
    +
  • + +
  • + +
    历史
    +
    &#xe61e;
    +
  • + +
  • + +
    解析
    +
    &#xe614;
    +
  • + +
  • + +
    收藏
    +
    &#xe6c4;
    +
  • + +
  • + +
    测试
    +
    &#xefb1;
    +
  • + +
  • + +
    点播
    +
    &#xe770;
    +
  • + +
  • + +
    主页
    +
    &#xe697;
    +
  • + +
  • + +
    书柜
    +
    &#xe71e;
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1760257974279') format('woff2'),
+       url('iconfont.woff?t=1760257974279') format('woff'),
+       url('iconfont.ttf?t=1760257974279') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + 下载 +
    +
    .icon-xiazai +
    +
  • + +
  • + +
    + 夸克 +
    +
    .icon-kuake +
    +
  • + +
  • + +
    + folder +
    +
    .icon-folder +
    +
  • + +
  • + +
    + file_blank +
    +
    .icon-file +
    +
  • + +
  • + +
    + file_zip +
    +
    .icon-file_zip +
    +
  • + +
  • + +
    + file_excel +
    +
    .icon-file_excel +
    +
  • + +
  • + +
    + file_ppt +
    +
    .icon-file_ppt +
    +
  • + +
  • + +
    + file_word +
    +
    .icon-file_word +
    +
  • + +
  • + +
    + file_pdf +
    +
    .icon-file_pdf +
    +
  • + +
  • + +
    + file_music +
    +
    .icon-file_music +
    +
  • + +
  • + +
    + file_video +
    +
    .icon-file_video +
    +
  • + +
  • + +
    + file_img +
    +
    .icon-file_img +
    +
  • + +
  • + +
    + file_ai +
    +
    .icon-file_ai +
    +
  • + +
  • + +
    + file_psd +
    +
    .icon-file_psd +
    +
  • + +
  • + +
    + file_bt +
    +
    .icon-file_bt +
    +
  • + +
  • + +
    + file_txt +
    +
    .icon-file_txt +
    +
  • + +
  • + +
    + file_exe +
    +
    .icon-file_exe +
    +
  • + +
  • + +
    + file_html +
    +
    .icon-file_html +
    +
  • + +
  • + +
    + file_cad +
    +
    .icon-file_cad +
    +
  • + +
  • + +
    + file_code +
    +
    .icon-file_code +
    +
  • + +
  • + +
    + file_flash +
    +
    .icon-file_flash +
    +
  • + +
  • + +
    + file_iso +
    +
    .icon-file_iso +
    +
  • + +
  • + +
    + file_cloud +
    +
    .icon-file_cloud +
    +
  • + +
  • + +
    + 文件夹 +
    +
    .icon-wenjianjia +
    +
  • + +
  • + +
    + 主页 +
    +
    .icon-zhuye +
    +
  • + +
  • + +
    + 设置 +
    +
    .icon-shezhi +
    +
  • + +
  • + +
    + 视频直播 +
    +
    .icon-shipinzhibo +
    +
  • + +
  • + +
    + 历史 +
    +
    .icon-lishi +
    +
  • + +
  • + +
    + 解析 +
    +
    .icon-jiexi +
    +
  • + +
  • + +
    + 收藏 +
    +
    .icon-shoucang +
    +
  • + +
  • + +
    + 测试 +
    +
    .icon-ceshi +
    +
  • + +
  • + +
    + 点播 +
    +
    .icon-dianbo +
    +
  • + +
  • + +
    + 主页 +
    +
    .icon-zhuye1 +
    +
  • + +
  • + +
    + 书柜 +
    +
    .icon-shugui +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    下载
    +
    #icon-xiazai
    +
  • + +
  • + +
    夸克
    +
    #icon-kuake
    +
  • + +
  • + +
    folder
    +
    #icon-folder
    +
  • + +
  • + +
    file_blank
    +
    #icon-file
    +
  • + +
  • + +
    file_zip
    +
    #icon-file_zip
    +
  • + +
  • + +
    file_excel
    +
    #icon-file_excel
    +
  • + +
  • + +
    file_ppt
    +
    #icon-file_ppt
    +
  • + +
  • + +
    file_word
    +
    #icon-file_word
    +
  • + +
  • + +
    file_pdf
    +
    #icon-file_pdf
    +
  • + +
  • + +
    file_music
    +
    #icon-file_music
    +
  • + +
  • + +
    file_video
    +
    #icon-file_video
    +
  • + +
  • + +
    file_img
    +
    #icon-file_img
    +
  • + +
  • + +
    file_ai
    +
    #icon-file_ai
    +
  • + +
  • + +
    file_psd
    +
    #icon-file_psd
    +
  • + +
  • + +
    file_bt
    +
    #icon-file_bt
    +
  • + +
  • + +
    file_txt
    +
    #icon-file_txt
    +
  • + +
  • + +
    file_exe
    +
    #icon-file_exe
    +
  • + +
  • + +
    file_html
    +
    #icon-file_html
    +
  • + +
  • + +
    file_cad
    +
    #icon-file_cad
    +
  • + +
  • + +
    file_code
    +
    #icon-file_code
    +
  • + +
  • + +
    file_flash
    +
    #icon-file_flash
    +
  • + +
  • + +
    file_iso
    +
    #icon-file_iso
    +
  • + +
  • + +
    file_cloud
    +
    #icon-file_cloud
    +
  • + +
  • + +
    文件夹
    +
    #icon-wenjianjia
    +
  • + +
  • + +
    主页
    +
    #icon-zhuye
    +
  • + +
  • + +
    设置
    +
    #icon-shezhi
    +
  • + +
  • + +
    视频直播
    +
    #icon-shipinzhibo
    +
  • + +
  • + +
    历史
    +
    #icon-lishi
    +
  • + +
  • + +
    解析
    +
    #icon-jiexi
    +
  • + +
  • + +
    收藏
    +
    #icon-shoucang
    +
  • + +
  • + +
    测试
    +
    #icon-ceshi
    +
  • + +
  • + +
    点播
    +
    #icon-dianbo
    +
  • + +
  • + +
    主页
    +
    #icon-zhuye1
    +
  • + +
  • + +
    书柜
    +
    #icon-shugui
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/dashboard/src/assets/icon_font/iconfont.css b/dashboard/src/assets/icon_font/iconfont.css new file mode 100644 index 0000000..f1d833b --- /dev/null +++ b/dashboard/src/assets/icon_font/iconfont.css @@ -0,0 +1,151 @@ +@font-face { + font-family: "iconfont"; /* Project id 5032989 */ + src: url('iconfont.woff2?t=1760257974279') format('woff2'), + url('iconfont.woff?t=1760257974279') format('woff'), + url('iconfont.ttf?t=1760257974279') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-xiazai:before { + content: "\fcb9"; +} + +.icon-kuake:before { + content: "\e67d"; +} + +.icon-folder:before { + content: "\e690"; +} + +.icon-file:before { + content: "\e692"; +} + +.icon-file_zip:before { + content: "\e698"; +} + +.icon-file_excel:before { + content: "\e699"; +} + +.icon-file_ppt:before { + content: "\e69a"; +} + +.icon-file_word:before { + content: "\e69b"; +} + +.icon-file_pdf:before { + content: "\e69c"; +} + +.icon-file_music:before { + content: "\e69d"; +} + +.icon-file_video:before { + content: "\e69e"; +} + +.icon-file_img:before { + content: "\e69f"; +} + +.icon-file_ai:before { + content: "\e6a1"; +} + +.icon-file_psd:before { + content: "\e6a2"; +} + +.icon-file_bt:before { + content: "\e6a3"; +} + +.icon-file_txt:before { + content: "\e6a4"; +} + +.icon-file_exe:before { + content: "\e6a5"; +} + +.icon-file_html:before { + content: "\e6a7"; +} + +.icon-file_cad:before { + content: "\e6a8"; +} + +.icon-file_code:before { + content: "\e6a9"; +} + +.icon-file_flash:before { + content: "\e6aa"; +} + +.icon-file_iso:before { + content: "\e6ab"; +} + +.icon-file_cloud:before { + content: "\e6ac"; +} + +.icon-wenjianjia:before { + content: "\e80c"; +} + +.icon-zhuye:before { + content: "\e71f"; +} + +.icon-shezhi:before { + content: "\e63a"; +} + +.icon-shipinzhibo:before { + content: "\e621"; +} + +.icon-lishi:before { + content: "\e61e"; +} + +.icon-jiexi:before { + content: "\e614"; +} + +.icon-shoucang:before { + content: "\e6c4"; +} + +.icon-ceshi:before { + content: "\efb1"; +} + +.icon-dianbo:before { + content: "\e770"; +} + +.icon-zhuye1:before { + content: "\e697"; +} + +.icon-shugui:before { + content: "\e71e"; +} + diff --git a/dashboard/src/assets/icon_font/iconfont.js b/dashboard/src/assets/icon_font/iconfont.js new file mode 100644 index 0000000..dbc87f8 --- /dev/null +++ b/dashboard/src/assets/icon_font/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_5032989='',(v=>{var l=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var c,t,a,m,z,p=function(l,h){h.parentNode.insertBefore(l,h)};if(l&&!v.__iconfont__svg__cssinject__){v.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}c=function(){var l,h=document.createElement("div");h.innerHTML=v._iconfont_svg_string_5032989,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(l=document.body).firstChild?p(h,l.firstChild):l.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(c,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),c()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(a=c,m=v.document,z=!1,V(),m.onreadystatechange=function(){"complete"==m.readyState&&(m.onreadystatechange=null,i())})}function i(){z||(z=!0,a())}function V(){try{m.documentElement.doScroll("left")}catch(l){return void setTimeout(V,50)}i()}})(window); \ No newline at end of file diff --git a/dashboard/src/assets/icon_font/iconfont.json b/dashboard/src/assets/icon_font/iconfont.json new file mode 100644 index 0000000..79298a7 --- /dev/null +++ b/dashboard/src/assets/icon_font/iconfont.json @@ -0,0 +1,247 @@ +{ + "id": "5032989", + "name": "drplayer", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "24473120", + "name": "下载", + "font_class": "xiazai", + "unicode": "fcb9", + "unicode_decimal": 64697 + }, + { + "icon_id": "25677250", + "name": "夸克", + "font_class": "kuake", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "5328033", + "name": "folder", + "font_class": "folder", + "unicode": "e690", + "unicode_decimal": 59024 + }, + { + "icon_id": "5328329", + "name": "file_blank", + "font_class": "file", + "unicode": "e692", + "unicode_decimal": 59026 + }, + { + "icon_id": "5330223", + "name": "file_zip", + "font_class": "file_zip", + "unicode": "e698", + "unicode_decimal": 59032 + }, + { + "icon_id": "5330370", + "name": "file_excel", + "font_class": "file_excel", + "unicode": "e699", + "unicode_decimal": 59033 + }, + { + "icon_id": "5330447", + "name": "file_ppt", + "font_class": "file_ppt", + "unicode": "e69a", + "unicode_decimal": 59034 + }, + { + "icon_id": "5330482", + "name": "file_word", + "font_class": "file_word", + "unicode": "e69b", + "unicode_decimal": 59035 + }, + { + "icon_id": "5330697", + "name": "file_pdf", + "font_class": "file_pdf", + "unicode": "e69c", + "unicode_decimal": 59036 + }, + { + "icon_id": "5331557", + "name": "file_music", + "font_class": "file_music", + "unicode": "e69d", + "unicode_decimal": 59037 + }, + { + "icon_id": "5331606", + "name": "file_video", + "font_class": "file_video", + "unicode": "e69e", + "unicode_decimal": 59038 + }, + { + "icon_id": "5332026", + "name": "file_img", + "font_class": "file_img", + "unicode": "e69f", + "unicode_decimal": 59039 + }, + { + "icon_id": "5332223", + "name": "file_ai", + "font_class": "file_ai", + "unicode": "e6a1", + "unicode_decimal": 59041 + }, + { + "icon_id": "5332227", + "name": "file_psd", + "font_class": "file_psd", + "unicode": "e6a2", + "unicode_decimal": 59042 + }, + { + "icon_id": "5332236", + "name": "file_bt", + "font_class": "file_bt", + "unicode": "e6a3", + "unicode_decimal": 59043 + }, + { + "icon_id": "5332262", + "name": "file_txt", + "font_class": "file_txt", + "unicode": "e6a4", + "unicode_decimal": 59044 + }, + { + "icon_id": "5332305", + "name": "file_exe", + "font_class": "file_exe", + "unicode": "e6a5", + "unicode_decimal": 59045 + }, + { + "icon_id": "5332367", + "name": "file_html", + "font_class": "file_html", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "5332434", + "name": "file_cad", + "font_class": "file_cad", + "unicode": "e6a8", + "unicode_decimal": 59048 + }, + { + "icon_id": "5332517", + "name": "file_code", + "font_class": "file_code", + "unicode": "e6a9", + "unicode_decimal": 59049 + }, + { + "icon_id": "5332600", + "name": "file_flash", + "font_class": "file_flash", + "unicode": "e6aa", + "unicode_decimal": 59050 + }, + { + "icon_id": "5332645", + "name": "file_iso", + "font_class": "file_iso", + "unicode": "e6ab", + "unicode_decimal": 59051 + }, + { + "icon_id": "5332706", + "name": "file_cloud", + "font_class": "file_cloud", + "unicode": "e6ac", + "unicode_decimal": 59052 + }, + { + "icon_id": "11448884", + "name": "文件夹", + "font_class": "wenjianjia", + "unicode": "e80c", + "unicode_decimal": 59404 + }, + { + "icon_id": "3819795", + "name": "主页", + "font_class": "zhuye", + "unicode": "e71f", + "unicode_decimal": 59167 + }, + { + "icon_id": "3864545", + "name": "设置", + "font_class": "shezhi", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "8604981", + "name": "视频直播", + "font_class": "shipinzhibo", + "unicode": "e621", + "unicode_decimal": 58913 + }, + { + "icon_id": "8659214", + "name": "历史", + "font_class": "lishi", + "unicode": "e61e", + "unicode_decimal": 58910 + }, + { + "icon_id": "9616330", + "name": "解析", + "font_class": "jiexi", + "unicode": "e614", + "unicode_decimal": 58900 + }, + { + "icon_id": "11819161", + "name": "收藏", + "font_class": "shoucang", + "unicode": "e6c4", + "unicode_decimal": 59076 + }, + { + "icon_id": "18538444", + "name": "测试", + "font_class": "ceshi", + "unicode": "efb1", + "unicode_decimal": 61361 + }, + { + "icon_id": "26401563", + "name": "点播", + "font_class": "dianbo", + "unicode": "e770", + "unicode_decimal": 59248 + }, + { + "icon_id": "28101990", + "name": "主页", + "font_class": "zhuye1", + "unicode": "e697", + "unicode_decimal": 59031 + }, + { + "icon_id": "36211588", + "name": "书柜", + "font_class": "shugui", + "unicode": "e71e", + "unicode_decimal": 59166 + } + ] +} diff --git a/dashboard/src/assets/icon_font/iconfont.ttf b/dashboard/src/assets/icon_font/iconfont.ttf new file mode 100644 index 0000000..041d918 Binary files /dev/null and b/dashboard/src/assets/icon_font/iconfont.ttf differ diff --git a/dashboard/src/assets/icon_font/iconfont.woff b/dashboard/src/assets/icon_font/iconfont.woff new file mode 100644 index 0000000..1b9204f Binary files /dev/null and b/dashboard/src/assets/icon_font/iconfont.woff differ diff --git a/dashboard/src/assets/icon_font/iconfont.woff2 b/dashboard/src/assets/icon_font/iconfont.woff2 new file mode 100644 index 0000000..a086862 Binary files /dev/null and b/dashboard/src/assets/icon_font/iconfont.woff2 differ diff --git a/dashboard/src/assets/logo.png b/dashboard/src/assets/logo.png new file mode 100644 index 0000000..a4faacb Binary files /dev/null and b/dashboard/src/assets/logo.png differ diff --git a/dashboard/src/components/About.vue b/dashboard/src/components/About.vue new file mode 100644 index 0000000..6b3f79c --- /dev/null +++ b/dashboard/src/components/About.vue @@ -0,0 +1,426 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/ActionDocCard.vue b/dashboard/src/components/ActionDocCard.vue new file mode 100644 index 0000000..138c81a --- /dev/null +++ b/dashboard/src/components/ActionDocCard.vue @@ -0,0 +1,729 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/AddressHistory.vue b/dashboard/src/components/AddressHistory.vue new file mode 100644 index 0000000..3f32d51 --- /dev/null +++ b/dashboard/src/components/AddressHistory.vue @@ -0,0 +1,377 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/ApiUrlManager.vue b/dashboard/src/components/ApiUrlManager.vue new file mode 100644 index 0000000..8a14d20 --- /dev/null +++ b/dashboard/src/components/ApiUrlManager.vue @@ -0,0 +1,505 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/BackupRestoreDialog.vue b/dashboard/src/components/BackupRestoreDialog.vue new file mode 100644 index 0000000..e9658b6 --- /dev/null +++ b/dashboard/src/components/BackupRestoreDialog.vue @@ -0,0 +1,599 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/Breadcrumb.vue b/dashboard/src/components/Breadcrumb.vue new file mode 100644 index 0000000..369a484 --- /dev/null +++ b/dashboard/src/components/Breadcrumb.vue @@ -0,0 +1,406 @@ + + + + + diff --git a/dashboard/src/components/CategoryModal.vue b/dashboard/src/components/CategoryModal.vue new file mode 100644 index 0000000..7424457 --- /dev/null +++ b/dashboard/src/components/CategoryModal.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/CategoryNavigation.vue b/dashboard/src/components/CategoryNavigation.vue new file mode 100644 index 0000000..107a6fe --- /dev/null +++ b/dashboard/src/components/CategoryNavigation.vue @@ -0,0 +1,442 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/FilterSection.vue b/dashboard/src/components/FilterSection.vue new file mode 100644 index 0000000..e3f5130 --- /dev/null +++ b/dashboard/src/components/FilterSection.vue @@ -0,0 +1,259 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/FloatingIframe.vue b/dashboard/src/components/FloatingIframe.vue new file mode 100644 index 0000000..8c46b0c --- /dev/null +++ b/dashboard/src/components/FloatingIframe.vue @@ -0,0 +1,619 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/FolderBreadcrumb.vue b/dashboard/src/components/FolderBreadcrumb.vue new file mode 100644 index 0000000..3a907bc --- /dev/null +++ b/dashboard/src/components/FolderBreadcrumb.vue @@ -0,0 +1,235 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/Footer.vue b/dashboard/src/components/Footer.vue new file mode 100644 index 0000000..c0f79e3 --- /dev/null +++ b/dashboard/src/components/Footer.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/dashboard/src/components/GlobalActionDialog.vue b/dashboard/src/components/GlobalActionDialog.vue new file mode 100644 index 0000000..829c201 --- /dev/null +++ b/dashboard/src/components/GlobalActionDialog.vue @@ -0,0 +1,706 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/GlobalToast.vue b/dashboard/src/components/GlobalToast.vue new file mode 100644 index 0000000..2ee4761 --- /dev/null +++ b/dashboard/src/components/GlobalToast.vue @@ -0,0 +1,65 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/Header.vue b/dashboard/src/components/Header.vue new file mode 100644 index 0000000..9b34116 --- /dev/null +++ b/dashboard/src/components/Header.vue @@ -0,0 +1,844 @@ + + + + + diff --git a/dashboard/src/components/HeaderToolbar.vue b/dashboard/src/components/HeaderToolbar.vue deleted file mode 100644 index e9f9891..0000000 --- a/dashboard/src/components/HeaderToolbar.vue +++ /dev/null @@ -1,77 +0,0 @@ - - - - - diff --git a/dashboard/src/components/Layout.vue b/dashboard/src/components/Layout.vue new file mode 100644 index 0000000..869cf29 --- /dev/null +++ b/dashboard/src/components/Layout.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/dashboard/src/components/PlayerSelector.vue b/dashboard/src/components/PlayerSelector.vue new file mode 100644 index 0000000..cdac8b8 --- /dev/null +++ b/dashboard/src/components/PlayerSelector.vue @@ -0,0 +1,210 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/ScrollToBottom.vue b/dashboard/src/components/ScrollToBottom.vue new file mode 100644 index 0000000..302b775 --- /dev/null +++ b/dashboard/src/components/ScrollToBottom.vue @@ -0,0 +1,210 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/SearchResults.vue b/dashboard/src/components/SearchResults.vue new file mode 100644 index 0000000..266c493 --- /dev/null +++ b/dashboard/src/components/SearchResults.vue @@ -0,0 +1,469 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/SearchSettingsModal.vue b/dashboard/src/components/SearchSettingsModal.vue new file mode 100644 index 0000000..64f8744 --- /dev/null +++ b/dashboard/src/components/SearchSettingsModal.vue @@ -0,0 +1,651 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/SearchVideoGrid.vue b/dashboard/src/components/SearchVideoGrid.vue new file mode 100644 index 0000000..422b676 --- /dev/null +++ b/dashboard/src/components/SearchVideoGrid.vue @@ -0,0 +1,687 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/SideBarMenu.vue b/dashboard/src/components/SideBarMenu.vue deleted file mode 100644 index d2c7f69..0000000 --- a/dashboard/src/components/SideBarMenu.vue +++ /dev/null @@ -1,156 +0,0 @@ - - - - - diff --git a/dashboard/src/components/SourceDialog.vue b/dashboard/src/components/SourceDialog.vue new file mode 100644 index 0000000..d86fd03 --- /dev/null +++ b/dashboard/src/components/SourceDialog.vue @@ -0,0 +1,590 @@ + + + + + + + diff --git a/dashboard/src/components/VideoCard.vue b/dashboard/src/components/VideoCard.vue new file mode 100644 index 0000000..02b8dd1 --- /dev/null +++ b/dashboard/src/components/VideoCard.vue @@ -0,0 +1,322 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/VideoGrid.vue b/dashboard/src/components/VideoGrid.vue new file mode 100644 index 0000000..06cf417 --- /dev/null +++ b/dashboard/src/components/VideoGrid.vue @@ -0,0 +1,986 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/VideoList.vue b/dashboard/src/components/VideoList.vue new file mode 100644 index 0000000..d57ed07 --- /dev/null +++ b/dashboard/src/components/VideoList.vue @@ -0,0 +1,1522 @@ + + + + + diff --git a/dashboard/src/components/actions/ActionDialog.vue b/dashboard/src/components/actions/ActionDialog.vue new file mode 100644 index 0000000..5fa090d --- /dev/null +++ b/dashboard/src/components/actions/ActionDialog.vue @@ -0,0 +1,497 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/ActionRenderer.vue b/dashboard/src/components/actions/ActionRenderer.vue new file mode 100644 index 0000000..6cb7c94 --- /dev/null +++ b/dashboard/src/components/actions/ActionRenderer.vue @@ -0,0 +1,571 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/ActionStateManager.js b/dashboard/src/components/actions/ActionStateManager.js new file mode 100644 index 0000000..6b13326 --- /dev/null +++ b/dashboard/src/components/actions/ActionStateManager.js @@ -0,0 +1,631 @@ +import { reactive, ref, computed } from 'vue' +import { ActionType, ActionErrorType } from './types.js' + +/** + * Action状态管理器 + * 负责管理Action的状态、历史记录和全局配置 + */ +class ActionStateManager { + constructor() { + // 当前活动的Action + this.currentAction = ref(null) + + // Action历史记录 + this.actionHistory = ref([]) + + // 全局配置 + this.globalConfig = reactive({ + // 默认超时时间(秒) + defaultTimeout: 30, + // 最大历史记录数 + maxHistorySize: 100, + // 是否启用调试模式 + debugMode: false, + // 默认主题 + theme: 'default', + // 是否允许多个Action同时显示 + allowMultiple: false, + // 默认弹窗配置 + defaultDialog: { + width: 400, + height: null, + canceledOnTouchOutside: true + } + }) + + // Action队列(当不允许多个Action时使用) + this.actionQueue = ref([]) + + // 错误处理器 + this.errorHandlers = new Map() + + // 事件监听器 + this.eventListeners = new Map() + + // 统计信息 + this.statistics = reactive({ + totalActions: 0, + successfulActions: 0, + canceledActions: 0, + errorActions: 0, + averageResponseTime: 0 + }) + } + + /** + * 显示Action + * @param {Object} config - Action配置 + * @param {Object} options - 显示选项 + * @returns {Promise} Action结果 + */ + async showAction(config, options = {}) { + try { + // 验证配置 + this.validateConfig(config) + + // 创建Action实例 + const action = this.createAction(config, options) + + // 检查是否允许多个Action + if (!this.globalConfig.allowMultiple && this.currentAction.value) { + if (options.force) { + // 强制关闭当前Action + await this.closeCurrentAction() + } else { + // 加入队列 + return this.enqueueAction(action) + } + } + + // 设置当前Action + this.currentAction.value = action + + // 添加到历史记录 + this.addToHistory(action) + + // 更新统计 + this.statistics.totalActions++ + + // 触发事件 + this.emit('action-show', action) + + // 启动超时计时器 + this.startTimeout(action) + + // 返回Promise + return new Promise((resolve, reject) => { + action.resolve = resolve + action.reject = reject + }) + + } catch (error) { + this.handleError(error, config) + throw error + } + } + + /** + * 创建Action实例 + * @param {Object} config - Action配置 + * @param {Object} options - 选项 + * @returns {Object} Action实例 + */ + createAction(config, options) { + const action = { + id: this.generateId(), + type: config.type, + config: { ...config }, + options: { ...options }, + status: 'pending', + visible: true, + startTime: Date.now(), + endTime: null, + result: null, + error: null, + timeout: null, + resolve: null, + reject: null + } + + // 合并全局配置 + this.mergeGlobalConfig(action) + + return action + } + + /** + * 合并全局配置 + * @param {Object} action - Action实例 + */ + mergeGlobalConfig(action) { + // 设置默认超时 + if (!action.config.timeout && this.globalConfig.defaultTimeout > 0) { + action.config.timeout = this.globalConfig.defaultTimeout + } + + // 合并弹窗配置 + if (!action.config.width) { + action.config.width = this.globalConfig.defaultDialog.width + } + if (!action.config.height) { + action.config.height = this.globalConfig.defaultDialog.height + } + if (action.config.canceledOnTouchOutside === undefined) { + action.config.canceledOnTouchOutside = this.globalConfig.defaultDialog.canceledOnTouchOutside + } + } + + /** + * 提交Action结果 + * @param {*} result - 结果数据 + */ + submitAction(result) { + const action = this.currentAction.value + if (!action) return + + action.status = 'completed' + action.result = result + action.endTime = Date.now() + action.visible = false + + // 清除超时 + this.clearTimeout(action) + + // 更新统计 + this.statistics.successfulActions++ + this.updateAverageResponseTime(action) + + // 触发事件 + this.emit('action-submit', { action, result }) + + // 解析Promise + if (action.resolve) { + action.resolve(result) + } + + // 处理下一个Action + this.processNextAction() + } + + /** + * 取消Action + * @param {string} reason - 取消原因 + */ + cancelAction(reason = 'user_cancel') { + const action = this.currentAction.value + if (!action) return + + action.status = 'canceled' + action.error = { type: 'cancel', reason } + action.endTime = Date.now() + action.visible = false + + // 清除超时 + this.clearTimeout(action) + + // 更新统计 + this.statistics.canceledActions++ + + // 触发事件 + this.emit('action-cancel', { action, reason }) + + // 拒绝Promise + if (action.reject) { + action.reject(new Error(`Action canceled: ${reason}`)) + } + + // 处理下一个Action + this.processNextAction() + } + + /** + * Action错误处理 + * @param {Error} error - 错误对象 + */ + actionError(error) { + const action = this.currentAction.value + if (!action) return + + action.status = 'error' + action.error = error + action.endTime = Date.now() + + // 清除超时 + this.clearTimeout(action) + + // 更新统计 + this.statistics.errorActions++ + + // 触发事件 + this.emit('action-error', { action, error }) + + // 处理错误 + this.handleError(error, action.config) + + // 拒绝Promise + if (action.reject) { + action.reject(error) + } + + // 处理下一个Action + this.processNextAction() + } + + /** + * 关闭当前Action + */ + async closeCurrentAction() { + if (this.currentAction.value) { + this.cancelAction('force_close') + } + } + + /** + * 将Action加入队列 + * @param {Object} action - Action实例 + * @returns {Promise} Action结果 + */ + enqueueAction(action) { + this.actionQueue.value.push(action) + + return new Promise((resolve, reject) => { + action.resolve = resolve + action.reject = reject + }) + } + + /** + * 处理下一个Action + */ + processNextAction() { + // 清除当前Action + this.currentAction.value = null + + // 处理队列中的下一个Action + if (this.actionQueue.value.length > 0) { + const nextAction = this.actionQueue.value.shift() + this.currentAction.value = nextAction + + // 添加到历史记录 + this.addToHistory(nextAction) + + // 更新统计 + this.statistics.totalActions++ + + // 触发事件 + this.emit('action-show', nextAction) + + // 启动超时计时器 + this.startTimeout(nextAction) + } + } + + /** + * 启动超时计时器 + * @param {Object} action - Action实例 + */ + startTimeout(action) { + if (!action.config.timeout || action.config.timeout <= 0) return + + action.timeout = setTimeout(() => { + if (action === this.currentAction.value) { + this.cancelAction('timeout') + } + }, action.config.timeout * 1000) + } + + /** + * 清除超时计时器 + * @param {Object} action - Action实例 + */ + clearTimeout(action) { + if (action.timeout) { + clearTimeout(action.timeout) + action.timeout = null + } + } + + /** + * 添加到历史记录 + * @param {Object} action - Action实例 + */ + addToHistory(action) { + this.actionHistory.value.unshift(action) + + // 限制历史记录大小 + if (this.actionHistory.value.length > this.globalConfig.maxHistorySize) { + this.actionHistory.value = this.actionHistory.value.slice(0, this.globalConfig.maxHistorySize) + } + } + + /** + * 更新平均响应时间 + * @param {Object} action - Action实例 + */ + updateAverageResponseTime(action) { + const responseTime = action.endTime - action.startTime + const total = this.statistics.successfulActions + const current = this.statistics.averageResponseTime + + this.statistics.averageResponseTime = Math.round( + (current * (total - 1) + responseTime) / total + ) + } + + /** + * 验证配置 + * @param {Object} config - Action配置 + */ + validateConfig(config) { + if (!config || typeof config !== 'object') { + throw new Error('Action配置不能为空') + } + + if (!config.type || !Object.values(ActionType).includes(config.type)) { + throw new Error(`无效的Action类型: ${config.type}`) + } + + // 验证actionId是必需的 + if (!config.actionId || typeof config.actionId !== 'string') { + throw new Error('actionId是必需的,且必须是字符串类型') + } + + // 根据类型进行特定验证 + switch (config.type) { + case ActionType.INPUT: + if (!config.msg && !config.img && !config.qr) { + throw new Error('InputAction必须包含消息、图片或二维码') + } + break + + case ActionType.MULTI_INPUT: + // 支持 input 和 inputs 两种字段名称 + const inputList = config.input || config.inputs + if (!inputList || !Array.isArray(inputList) || inputList.length === 0) { + throw new Error('MultiInputAction必须包含输入项列表') + } + break + + case ActionType.MULTI_INPUT_X: + // 支持 input 和 inputs 两种字段名称 + const inputListX = config.input || config.inputs + if (!inputListX || !Array.isArray(inputListX) || inputListX.length === 0) { + throw new Error('MultiInputXAction必须包含输入项列表') + } + break + + case ActionType.MENU: + case ActionType.SELECT: + // 支持 option 和 options 两种字段名称 + const optionList = config.option || config.options + if (!optionList || !Array.isArray(optionList) || optionList.length === 0) { + throw new Error('MenuAction必须包含选项列表') + } + break + + case ActionType.MSGBOX: + if (!config.msg && !config.img && !config.qr) { + throw new Error('MsgBoxAction必须包含消息、图片或二维码') + } + break + + case ActionType.WEBVIEW: + if (!config.url) { + throw new Error('WebViewAction必须包含URL') + } + break + + case ActionType.HELP: + if (!config.msg && !config.details && !config.steps) { + throw new Error('HelpAction必须包含帮助内容') + } + break + } + } + + /** + * 错误处理 + * @param {Error} error - 错误对象 + * @param {Object} config - Action配置 + */ + handleError(error, config) { + // 记录错误 + if (this.globalConfig.debugMode) { + console.error('Action错误:', error, config) + } + + // 调用错误处理器 + const handler = this.errorHandlers.get(error.type) || this.errorHandlers.get('default') + if (handler) { + try { + handler(error, config) + } catch (handlerError) { + console.error('错误处理器异常:', handlerError) + } + } + + // 触发错误事件 + this.emit('error', { error, config }) + } + + /** + * 注册错误处理器 + * @param {string} type - 错误类型 + * @param {Function} handler - 处理函数 + */ + registerErrorHandler(type, handler) { + this.errorHandlers.set(type, handler) + } + + /** + * 注册事件监听器 + * @param {string} event - 事件名称 + * @param {Function} listener - 监听函数 + */ + on(event, listener) { + if (!this.eventListeners.has(event)) { + this.eventListeners.set(event, []) + } + this.eventListeners.get(event).push(listener) + } + + /** + * 移除事件监听器 + * @param {string} event - 事件名称 + * @param {Function} listener - 监听函数 + */ + off(event, listener) { + const listeners = this.eventListeners.get(event) + if (listeners) { + const index = listeners.indexOf(listener) + if (index > -1) { + listeners.splice(index, 1) + } + } + } + + /** + * 触发事件 + * @param {string} event - 事件名称 + * @param {*} data - 事件数据 + */ + emit(event, data) { + const listeners = this.eventListeners.get(event) + if (listeners) { + listeners.forEach(listener => { + try { + listener(data) + } catch (error) { + console.error('事件监听器异常:', error) + } + }) + } + } + + /** + * 生成唯一ID + * @returns {string} 唯一ID + */ + generateId() { + return `action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` + } + + /** + * 获取Action历史记录 + * @param {Object} filter - 过滤条件 + * @returns {Array} 历史记录 + */ + getHistory(filter = {}) { + let history = [...this.actionHistory.value] + + // 按类型过滤 + if (filter.type) { + history = history.filter(action => action.type === filter.type) + } + + // 按状态过滤 + if (filter.status) { + history = history.filter(action => action.status === filter.status) + } + + // 按时间范围过滤 + if (filter.startTime) { + history = history.filter(action => action.startTime >= filter.startTime) + } + if (filter.endTime) { + history = history.filter(action => action.endTime <= filter.endTime) + } + + // 限制数量 + if (filter.limit) { + history = history.slice(0, filter.limit) + } + + return history + } + + /** + * 清除历史记录 + * @param {Object} filter - 过滤条件 + */ + clearHistory(filter = {}) { + if (Object.keys(filter).length === 0) { + // 清除所有历史记录 + this.actionHistory.value = [] + } else { + // 按条件清除 + const toKeep = this.actionHistory.value.filter(action => { + if (filter.type && action.type === filter.type) return false + if (filter.status && action.status === filter.status) return false + if (filter.before && action.startTime < filter.before) return false + return true + }) + this.actionHistory.value = toKeep + } + } + + /** + * 更新全局配置 + * @param {Object} config - 配置对象 + */ + updateConfig(config) { + Object.assign(this.globalConfig, config) + } + + /** + * 重置统计信息 + */ + resetStatistics() { + this.statistics.totalActions = 0 + this.statistics.successfulActions = 0 + this.statistics.canceledActions = 0 + this.statistics.errorActions = 0 + this.statistics.averageResponseTime = 0 + } + + /** + * 销毁管理器 + */ + destroy() { + // 关闭当前Action + if (this.currentAction.value) { + this.cancelAction('destroy') + } + + // 清除队列 + this.actionQueue.value.forEach(action => { + if (action.reject) { + action.reject(new Error('ActionStateManager destroyed')) + } + }) + this.actionQueue.value = [] + + // 清除事件监听器 + this.eventListeners.clear() + + // 清除错误处理器 + this.errorHandlers.clear() + + // 清除历史记录 + this.actionHistory.value = [] + } +} + +// 创建全局实例 +const actionStateManager = new ActionStateManager() + +// 导出管理器类和全局实例 +export { ActionStateManager, actionStateManager } + +// 导出便捷方法 +export const showAction = (config, options) => actionStateManager.showAction(config, options) +export const submitAction = (result) => actionStateManager.submitAction(result) +export const cancelAction = (reason) => actionStateManager.cancelAction(reason) +export const actionError = (error) => actionStateManager.actionError(error) + +// 导出计算属性 +export const currentAction = computed(() => actionStateManager.currentAction.value) +export const actionHistory = computed(() => actionStateManager.actionHistory.value) +export const actionQueue = computed(() => actionStateManager.actionQueue.value) +export const statistics = computed(() => actionStateManager.statistics) +export const globalConfig = computed(() => actionStateManager.globalConfig) \ No newline at end of file diff --git a/dashboard/src/components/actions/HelpAction.vue b/dashboard/src/components/actions/HelpAction.vue new file mode 100644 index 0000000..4dd841f --- /dev/null +++ b/dashboard/src/components/actions/HelpAction.vue @@ -0,0 +1,1598 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/InputAction.vue b/dashboard/src/components/actions/InputAction.vue new file mode 100644 index 0000000..3fb05d8 --- /dev/null +++ b/dashboard/src/components/actions/InputAction.vue @@ -0,0 +1,1692 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/MenuAction.vue b/dashboard/src/components/actions/MenuAction.vue new file mode 100644 index 0000000..f6993c2 --- /dev/null +++ b/dashboard/src/components/actions/MenuAction.vue @@ -0,0 +1,1814 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/MsgBoxAction.vue b/dashboard/src/components/actions/MsgBoxAction.vue new file mode 100644 index 0000000..ddbf1aa --- /dev/null +++ b/dashboard/src/components/actions/MsgBoxAction.vue @@ -0,0 +1,1130 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/MultiInputAction.vue b/dashboard/src/components/actions/MultiInputAction.vue new file mode 100644 index 0000000..11d3719 --- /dev/null +++ b/dashboard/src/components/actions/MultiInputAction.vue @@ -0,0 +1,2706 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/README.md b/dashboard/src/components/actions/README.md new file mode 100644 index 0000000..ec87a06 --- /dev/null +++ b/dashboard/src/components/actions/README.md @@ -0,0 +1,444 @@ +# Action组件系统 + +Action组件系统是一个功能强大的Vue.js交互式UI组件库,提供了丰富的用户交互功能,包括输入、选择、消息提示、网页浏览等多种组件类型。 + +## 🚀 快速开始 + +### 安装和配置 + +1. 在`main.js`中注册Action组件: + +```javascript +import { createApp } from 'vue' +import App from './App.vue' +import ActionComponents from '@/components/actions' + +const app = createApp(App) +app.use(ActionComponents) +app.mount('#app') +``` + +2. 在组件中使用: + +```javascript +import { Actions } from '@/components/actions' + +// 显示输入框 +const result = await Actions.input({ + title: '请输入', + message: '请输入您的姓名:', + placeholder: '姓名' +}) +``` + +## 📦 组件列表 + +### 核心组件 + +- **ActionRenderer** - 主渲染器组件 +- **ActionDialog** - 弹窗容器组件 +- **ActionStateManager** - 状态管理器 + +### 交互组件 + +- **InputAction** - 单项输入组件 +- **MultiInputAction** - 多项输入组件 +- **MenuAction** - 菜单选择组件 +- **SelectAction** - 下拉选择组件 +- **MsgBoxAction** - 消息框组件 +- **WebViewAction** - 网页视图组件 +- **HelpAction** - 帮助页面组件 + +## 🎯 使用方法 + +### 1. 基础输入 (InputAction) + +```javascript +// 简单输入 +const name = await Actions.input({ + title: '用户信息', + message: '请输入您的姓名:', + placeholder: '请输入姓名', + required: true, + timeout: 30000 +}) + +// 带验证的输入 +const email = await Actions.input({ + title: '邮箱验证', + message: '请输入邮箱地址:', + placeholder: 'example@email.com', + validation: { + type: 'email', + required: true + }, + helpText: '请输入有效的邮箱地址' +}) + +// 多行文本输入 +const description = await Actions.input({ + title: '描述信息', + message: '请输入描述:', + type: 'textarea', + rows: 4, + maxLength: 500 +}) +``` + +### 2. 多项输入 (MultiInputAction) + +```javascript +const userInfo = await Actions.multiInput({ + title: '用户注册', + message: '请填写注册信息:', + inputs: [ + { + key: 'username', + label: '用户名', + placeholder: '请输入用户名', + required: true, + validation: { + minLength: 3, + maxLength: 20, + pattern: '^[a-zA-Z0-9_]+$' + } + }, + { + key: 'email', + label: '邮箱', + placeholder: '请输入邮箱', + required: true, + validation: { type: 'email' } + }, + { + key: 'password', + label: '密码', + type: 'password', + required: true, + validation: { minLength: 6 } + } + ] +}) +``` + +### 3. 菜单选择 (MenuAction) + +```javascript +// 单选菜单 +const action = await Actions.menu({ + title: '选择操作', + message: '请选择要执行的操作:', + options: [ + { + key: 'create', + title: '创建', + description: '创建新项目', + icon: '➕' + }, + { + key: 'edit', + title: '编辑', + description: '编辑现有项目', + icon: '✏️' + } + ], + searchable: true +}) + +// 多选菜单 +const selectedItems = await Actions.menu({ + title: '选择功能', + options: [...], + multiple: true, + maxSelections: 3 +}) +``` + +### 4. 下拉选择 (SelectAction) + +```javascript +// 单选下拉 +const country = await Actions.select({ + title: '选择国家', + message: '请选择您的国家:', + options: [ + { key: 'cn', title: '中国' }, + { key: 'us', title: '美国' }, + { key: 'jp', title: '日本' } + ], + searchable: true +}) + +// 多选下拉 +const skills = await Actions.select({ + title: '技能选择', + options: [...], + multiple: true, + defaultSelected: ['javascript', 'vue'] +}) +``` + +### 5. 消息提示 (MsgBoxAction) + +```javascript +// 基础消息类型 +await Actions.alert('这是一个提醒消息') +await Actions.info('这是信息提示') +await Actions.success('操作成功!') +await Actions.warning('这是警告信息') +await Actions.error('发生错误!') + +// 确认对话框 +const confirmed = await Actions.confirm({ + title: '确认删除', + message: '您确定要删除这个项目吗?', + details: '删除后将无法恢复' +}) + +// 带进度条的消息 +const progressAction = Actions.progress({ + title: '处理中', + message: '正在处理,请稍候...', + progress: 0 +}) + +// 更新进度 +progressAction.updateProgress(50, '处理中... 50%') +progressAction.complete('处理完成!') +``` + +### 6. 网页视图 (WebViewAction) + +```javascript +const result = await Actions.webView({ + title: '网页浏览', + url: 'https://www.example.com', + width: '80%', + height: '70%', + showToolbar: true, + allowFullscreen: true +}) +``` + +### 7. 帮助页面 (HelpAction) + +```javascript +await Actions.help({ + title: '使用帮助', + message: '欢迎使用本系统!', + details: '这里是详细的帮助信息...', + steps: [ + '第一步:登录系统', + '第二步:选择功能', + '第三步:开始使用' + ], + faq: [ + { + question: '如何重置密码?', + answer: '点击登录页面的"忘记密码"链接' + } + ], + links: [ + { + title: '官方文档', + url: 'https://docs.example.com' + } + ] +}) +``` + +## ⚙️ 配置选项 + +### 全局配置 + +```javascript +import { ActionStateManager } from '@/components/actions' + +// 设置全局配置 +ActionStateManager.setConfig({ + defaultTimeout: 30000, // 默认超时时间 + maxHistorySize: 100, // 最大历史记录数 + debugMode: true, // 调试模式 + theme: 'light', // 主题 + allowMultiple: false, // 是否允许多个Action同时显示 + defaultDialog: { // 默认弹窗配置 + width: '500px', + height: 'auto', + maskClosable: true, + escClosable: true + } +}) +``` + +### 通用配置选项 + +所有Action组件都支持以下通用配置: + +```javascript +{ + title: '标题', // 弹窗标题 + message: '消息内容', // 主要消息 + timeout: 30000, // 超时时间(毫秒) + width: '500px', // 弹窗宽度 + height: 'auto', // 弹窗高度 + maskClosable: true, // 点击遮罩关闭 + escClosable: true, // ESC键关闭 + showCancel: true, // 显示取消按钮 + confirmText: '确定', // 确认按钮文本 + cancelText: '取消', // 取消按钮文本 + image: 'path/to/image.jpg', // 显示图片 + qrcode: 'QR码内容' // 显示二维码 +} +``` + +## 🎨 样式定制 + +### CSS变量 + +Action组件使用CSS变量进行样式定制: + +```css +:root { + --action-primary-color: #1890ff; + --action-success-color: #52c41a; + --action-warning-color: #faad14; + --action-error-color: #ff4d4f; + --action-border-radius: 6px; + --action-box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + --action-font-size: 14px; + --action-line-height: 1.5; +} +``` + +### 主题切换 + +```javascript +// 切换到暗色主题 +ActionStateManager.setTheme('dark') + +// 切换到亮色主题 +ActionStateManager.setTheme('light') +``` + +## 📊 状态管理 + +### 获取当前状态 + +```javascript +import { ActionStateManager, currentAction, actionHistory } from '@/components/actions' + +// 获取当前Action +console.log(currentAction.value) + +// 获取历史记录 +console.log(actionHistory.value) + +// 获取统计信息 +console.log(ActionStateManager.getStats()) +``` + +### 事件监听 + +```javascript +// 监听Action显示事件 +ActionStateManager.on('action:show', (action) => { + console.log('Action显示:', action) +}) + +// 监听Action提交事件 +ActionStateManager.on('action:submit', (action, result) => { + console.log('Action提交:', action, result) +}) + +// 监听Action取消事件 +ActionStateManager.on('action:cancel', (action) => { + console.log('Action取消:', action) +}) +``` + +## 🔧 高级用法 + +### 自定义验证 + +```javascript +const result = await Actions.input({ + title: '自定义验证', + validation: { + custom: (value) => { + if (value.length < 3) { + return '长度不能少于3个字符' + } + if (!/^[a-zA-Z]+$/.test(value)) { + return '只能包含字母' + } + return true + } + } +}) +``` + +### 动态选项 + +```javascript +const result = await Actions.select({ + title: '动态选项', + options: async (searchText) => { + // 异步获取选项 + const response = await fetch(`/api/search?q=${searchText}`) + return await response.json() + }, + searchable: true +}) +``` + +### 链式调用 + +```javascript +// 链式调用多个Action +const name = await Actions.input({ message: '请输入姓名' }) +const age = await Actions.input({ message: '请输入年龄', type: 'number' }) +const confirmed = await Actions.confirm({ message: `确认信息:${name}, ${age}岁?` }) + +if (confirmed) { + await Actions.success('信息确认成功!') +} +``` + +## 🐛 错误处理 + +```javascript +try { + const result = await Actions.input({ + title: '输入测试', + message: '请输入内容', + timeout: 10000 + }) + console.log('输入结果:', result) +} catch (error) { + if (error.type === 'timeout') { + console.log('操作超时') + } else if (error.type === 'cancel') { + console.log('用户取消') + } else { + console.error('其他错误:', error) + } +} +``` + +## 📝 最佳实践 + +1. **合理设置超时时间** - 根据操作复杂度设置合适的超时时间 +2. **提供清晰的提示信息** - 使用简洁明了的标题和消息 +3. **适当的验证** - 为用户输入提供必要的验证和提示 +4. **错误处理** - 始终使用try-catch处理Action调用 +5. **用户体验** - 避免同时显示过多Action,保持界面简洁 + +## 🔗 相关链接 + +- [Vue.js官方文档](https://vuejs.org/) +- [Arco Design Vue](https://arco.design/vue) +- [项目GitHub仓库](https://github.com/your-repo) + +## 📄 许可证 + +MIT License \ No newline at end of file diff --git a/dashboard/src/components/actions/WebViewAction.vue b/dashboard/src/components/actions/WebViewAction.vue new file mode 100644 index 0000000..04759b8 --- /dev/null +++ b/dashboard/src/components/actions/WebViewAction.vue @@ -0,0 +1,1096 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/actions/index.js b/dashboard/src/components/actions/index.js new file mode 100644 index 0000000..fb8060d --- /dev/null +++ b/dashboard/src/components/actions/index.js @@ -0,0 +1,219 @@ +// Action组件统一导出文件 + +// 导入所有组件 +import ActionRenderer from './ActionRenderer.vue' +import ActionDialog from './ActionDialog.vue' +import InputAction from './InputAction.vue' +import MultiInputAction from './MultiInputAction.vue' +import MenuAction from './MenuAction.vue' + +import MsgBoxAction from './MsgBoxAction.vue' +import WebViewAction from './WebViewAction.vue' +import HelpAction from './HelpAction.vue' + +// 导入类型定义和工具函数 +import * as types from './types.js' + +// 导入状态管理器 +import { + ActionStateManager, + actionStateManager, + showAction, + submitAction, + cancelAction, + actionError, + currentAction, + actionHistory, + actionQueue, + statistics, + globalConfig +} from './ActionStateManager.js' + +// 导入样式 +import './styles.css' + +// 组件列表 +const components = { + ActionRenderer, + ActionDialog, + InputAction, + MultiInputAction, + MenuAction, + MsgBoxAction, + WebViewAction, + HelpAction +} + +// Vue插件安装函数 +const install = (app, options = {}) => { + // 注册所有组件 + Object.keys(components).forEach(name => { + app.component(name, components[name]) + }) + + // 配置全局状态管理器 + if (options.config) { + actionStateManager.updateConfig(options.config) + } + + // 注册全局属性 + app.config.globalProperties.$actionManager = actionStateManager + app.config.globalProperties.$showAction = showAction + + // 提供依赖注入 + app.provide('actionManager', actionStateManager) + app.provide('showAction', showAction) +} + +// 默认导出(Vue插件) +export default { + install, + ...components +} + +// 单独导出组件 +export { + ActionRenderer, + ActionDialog, + InputAction, + MultiInputAction, + MenuAction, + MsgBoxAction, + WebViewAction, + HelpAction +} + +// 导出类型和工具 +export { + types, + ActionStateManager, + actionStateManager, + showAction, + submitAction, + cancelAction, + actionError, + currentAction, + actionHistory, + actionQueue, + statistics, + globalConfig +} + +// 导出便捷方法 +export const Actions = { + // 显示输入框 + input: (config) => showAction({ ...config, type: types.ActionType.INPUT }), + + // 显示多行编辑框 + edit: (config) => showAction({ ...config, type: types.ActionType.EDIT }), + + // 显示多输入框 + multiInput: (config) => showAction({ ...config, type: types.ActionType.MULTI_INPUT }), + + // 显示增强多输入框 + multiInputX: (config) => showAction({ ...config, type: types.ActionType.MULTI_INPUT_X }), + + // 显示菜单 + menu: (config) => showAction({ ...config, type: types.ActionType.MENU }), + + // 显示选择框 + select: (config) => showAction({ ...config, type: types.ActionType.SELECT }), + + // 显示消息框 + msgBox: (config) => showAction({ ...config, type: types.ActionType.MSGBOX }), + + // 显示网页视图 + webView: (config) => showAction({ ...config, type: types.ActionType.WEBVIEW }), + + // 显示帮助 + help: (config) => showAction({ ...config, type: types.ActionType.HELP }), + + // 显示确认对话框 + confirm: (message, title = '确认') => showAction({ + actionId: `confirm-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_CANCEL + }), + + // 显示警告对话框 + alert: (message, title = '提示') => showAction({ + actionId: `alert-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_ONLY + }), + + // 显示信息对话框 + info: (message, title = '信息') => showAction({ + actionId: `info-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_ONLY, + icon: 'info' + }), + + // 显示成功对话框 + success: (message, title = '成功') => showAction({ + actionId: `success-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_ONLY, + icon: 'success' + }), + + // 显示错误对话框 + error: (message, title = '错误') => showAction({ + actionId: `error-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_ONLY, + icon: 'error' + }), + + // 显示警告对话框 + warning: (message, title = '警告') => showAction({ + actionId: `warning-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.OK_ONLY, + icon: 'warning' + }), + + // 显示加载对话框 + loading: (message = '加载中...', title = '请稍候') => showAction({ + actionId: `loading-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.NONE, + showProgress: true + }), + + // 显示进度对话框 + progress: (message, title = '进度', progress = 0) => showAction({ + actionId: `progress-${Date.now()}`, + type: types.ActionType.MSGBOX, + msg: message, + title, + button: types.ButtonType.CANCEL_ONLY, + showProgress: true, + progress + }) +} + +// 导出配置创建函数 +export const createActionConfig = types.createActionConfig +export const createInputActionConfig = types.createInputActionConfig +export const createMultiInputActionConfig = types.createMultiInputActionConfig +export const createMenuActionConfig = types.createMenuActionConfig + +export const createMsgBoxActionConfig = types.createMsgBoxActionConfig +export const createWebViewActionConfig = types.createWebViewActionConfig +export const createHelpActionConfig = types.createHelpActionConfig \ No newline at end of file diff --git a/dashboard/src/components/actions/styles.css b/dashboard/src/components/actions/styles.css new file mode 100644 index 0000000..88e62db --- /dev/null +++ b/dashboard/src/components/actions/styles.css @@ -0,0 +1,575 @@ +/* Action组件样式系统 */ + +:root { + /* 颜色变量 */ + --action-primary: #1890ff; + --action-primary-hover: #40a9ff; + --action-primary-active: #096dd9; + --action-success: #52c41a; + --action-warning: #faad14; + --action-error: #f5222d; + --action-text: #262626; + --action-text-secondary: #8c8c8c; + --action-text-disabled: #bfbfbf; + --action-border: #d9d9d9; + --action-border-hover: #40a9ff; + --action-background: #ffffff; + --action-background-light: #fafafa; + --action-background-disabled: #f5f5f5; + + /* 兼容性变量 - 支持action-color-前缀 */ + --action-color-primary: var(--action-primary); + --action-color-primary-light: var(--action-primary-hover); + --action-color-primary-dark: var(--action-primary-active); + --action-color-primary-rgb: 24, 144, 255; + --action-color-secondary: #6c757d; + --action-color-secondary-rgb: 108, 117, 125; + --action-color-success: var(--action-success); + --action-color-warning: var(--action-warning); + --action-color-error: var(--action-error); + --action-color-text: var(--action-text); + --action-color-text-secondary: var(--action-text-secondary); + --action-color-text-disabled: var(--action-text-disabled); + --action-color-border: var(--action-border); + --action-color-border-hover: var(--action-border-hover); + --action-color-bg: var(--action-background); + --action-color-bg-light: var(--action-background-light); + --action-color-bg-disabled: var(--action-background-disabled); + + /* 尺寸变量 */ + --action-border-radius: 6px; + --action-border-radius-sm: 4px; + --action-border-radius-lg: 8px; + --action-padding: 16px; + --action-padding-sm: 8px; + --action-padding-lg: 24px; + --action-margin: 8px; + --action-margin-sm: 4px; + --action-margin-lg: 16px; + + /* 间距变量 */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + + /* 圆角变量 */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + + /* 字体大小变量 */ + --font-size-xs: 12px; + --font-size-sm: 14px; + --font-size-md: 16px; + --font-size-lg: 18px; + --font-size-xl: 20px; + + /* 阴影变量 */ + --action-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + --action-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.15); + --action-shadow-active: 0 0 0 2px rgba(24, 144, 255, 0.2); + + /* 动画变量 */ + --action-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + --action-transition-fast: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + --action-transition-duration: 0.3s; + + /* 扩展阴影变量 */ + --action-shadow-small: 0 1px 3px rgba(0, 0, 0, 0.12); + --action-shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.1); + --action-shadow-large: 0 10px 25px rgba(0, 0, 0, 0.15); + --action-shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.2); +} + +/* 基础重置 */ +.action-component * { + box-sizing: border-box; +} + +/* 弹窗遮罩 */ +.action-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.45); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + animation: actionFadeIn 0.3s ease-out; +} + +.action-mask.closing { + animation: actionFadeOut 0.3s ease-out; +} + +/* 弹窗容器 */ +.action-dialog { + background: var(--action-background); + border-radius: var(--action-border-radius-lg); + box-shadow: var(--action-shadow); + max-width: 90vw; + max-height: 90vh; + overflow: hidden; + animation: actionSlideIn 0.3s ease-out; + position: relative; +} + +.action-dialog.closing { + animation: actionSlideOut 0.3s ease-out; +} + +/* 弹窗头部 */ +.action-dialog-header { + padding: var(--action-padding) var(--action-padding) 0; + border-bottom: 1px solid var(--action-border); + margin-bottom: var(--action-padding); +} + +.action-dialog-title { + font-size: 16px; + font-weight: 600; + color: var(--action-text); + margin: 0; + padding-bottom: var(--action-padding-sm); +} + +/* 弹窗内容 */ +.action-dialog-content { + padding: 0 var(--action-padding); + /* 移除 max-height 和 overflow 设置,让子元素处理滚动 */ +} + +/* 弹窗底部 */ +.action-dialog-footer { + padding: var(--action-padding); + border-top: 1px solid var(--action-border); + margin-top: var(--action-padding); + display: flex; + justify-content: flex-end; + gap: var(--action-margin); +} + +/* 关闭按钮 */ +.action-dialog-close { + position: absolute; + top: var(--action-padding-sm); + right: var(--action-padding-sm); + width: 32px; + height: 32px; + border: none; + background: transparent; + cursor: pointer; + border-radius: var(--action-border-radius-sm); + display: flex; + align-items: center; + justify-content: center; + color: var(--action-text-secondary); + transition: var(--action-transition-fast); +} + +.action-dialog-close:hover { + background: var(--action-background-light); + color: var(--action-text); +} + +/* 按钮样式 */ +.action-button { + padding: var(--action-padding-sm) var(--action-padding); + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + background: var(--action-background); + color: var(--action-text); + cursor: pointer; + font-size: 14px; + line-height: 1.5; + transition: var(--action-transition); + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 80px; + user-select: none; +} + +.action-button:hover { + border-color: var(--action-border-hover); + color: var(--action-primary); +} + +.action-button:active { + transform: translateY(1px); +} + +.action-button:disabled { + background: var(--action-background-disabled); + border-color: var(--action-border); + color: var(--action-text-disabled); + cursor: not-allowed; +} + +.action-button-primary { + background: var(--action-primary); + border-color: var(--action-primary); + color: white; +} + +.action-button-primary:hover { + background: var(--action-primary-hover); + border-color: var(--action-primary-hover); + color: white; +} + +.action-button-primary:active { + background: var(--action-primary-active); + border-color: var(--action-primary-active); +} + +.action-button-danger { + background: var(--action-error); + border-color: var(--action-error); + color: white; +} + +.action-button-danger:hover { + background: #ff4d4f; + border-color: #ff4d4f; + color: white; +} + +/* 输入框样式 */ +.action-input { + width: 100%; + padding: var(--action-padding-sm) 12px; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + font-size: 14px; + line-height: 1.5; + color: var(--action-text); + background: var(--action-background); + transition: var(--action-transition); +} + +.action-input:focus { + border-color: var(--action-primary); + outline: none; + box-shadow: var(--action-shadow-active); +} + +.action-input:disabled { + background: var(--action-background-disabled); + color: var(--action-text-disabled); + cursor: not-allowed; +} + +.action-input.error { + border-color: var(--action-error); +} + +.action-input.error:focus { + box-shadow: 0 0 0 2px rgba(245, 34, 45, 0.2); +} + +/* 文本域样式 */ +.action-textarea { + resize: vertical; + min-height: 80px; + font-family: inherit; +} + +/* 标签样式 */ +.action-label { + display: block; + margin-bottom: var(--action-margin-sm); + font-size: 14px; + font-weight: 500; + color: var(--action-text); +} + +.action-label.required::after { + content: ' *'; + color: var(--action-error); +} + +/* 提示文本样式 */ +.action-tip { + font-size: 12px; + color: var(--action-text-secondary); + margin-top: var(--action-margin-sm); + line-height: 1.4; +} + +.action-tip.error { + color: var(--action-error); +} + +/* 表单项样式 */ +.action-form-item { + margin-bottom: var(--action-padding); +} + +.action-form-item:last-child { + margin-bottom: 0; +} + +/* 选项样式 */ +.action-option { + padding: 12px; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + cursor: pointer; + transition: var(--action-transition); + background: var(--action-background); + margin-bottom: var(--action-margin); + display: flex; + align-items: center; + user-select: none; +} + +.action-option:hover { + background: var(--action-background-light); + border-color: var(--action-primary); +} + +.action-option.selected { + background: var(--action-primary); + border-color: var(--action-primary); + color: white; +} + +.action-option.disabled { + background: var(--action-background-disabled); + color: var(--action-text-disabled); + cursor: not-allowed; +} + +/* 选项网格布局 */ +.action-options-grid { + display: grid; + gap: var(--action-margin); +} + +.action-options-grid.columns-1 { grid-template-columns: 1fr; } +.action-options-grid.columns-2 { grid-template-columns: repeat(2, 1fr); } +.action-options-grid.columns-3 { grid-template-columns: repeat(3, 1fr); } +.action-options-grid.columns-4 { grid-template-columns: repeat(4, 1fr); } + +/* 复选框样式 */ +.action-checkbox { + width: 16px; + height: 16px; + margin-right: var(--action-margin); + accent-color: var(--action-primary); +} + +/* 图片样式 */ +.action-image { + max-width: 100%; + height: auto; + border-radius: var(--action-border-radius); + margin: var(--action-margin) 0; + cursor: pointer; + transition: var(--action-transition); +} + +.action-image:hover { + transform: scale(1.02); + box-shadow: var(--action-shadow-hover); +} + +/* 二维码样式 */ +.action-qrcode { + text-align: center; + margin: var(--action-padding) 0; +} + +.action-qrcode img { + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); +} + +/* WebView样式 */ +.action-webview { + width: 100%; + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius); + background: var(--action-background); +} + +/* 加载状态 */ +.action-loading { + display: flex; + align-items: center; + justify-content: center; + padding: var(--action-padding-lg); + color: var(--action-text-secondary); +} + +.action-loading::before { + content: ''; + width: 20px; + height: 20px; + border: 2px solid var(--action-border); + border-top-color: var(--action-primary); + border-radius: 50%; + animation: actionSpin 1s linear infinite; + margin-right: var(--action-margin); +} + +/* 错误状态 */ +.action-error { + padding: var(--action-padding); + background: #fff2f0; + border: 1px solid #ffccc7; + border-radius: var(--action-border-radius); + color: var(--action-error); + margin: var(--action-margin) 0; +} + +/* 成功状态 */ +.action-success { + padding: var(--action-padding); + background: #f6ffed; + border: 1px solid #b7eb8f; + border-radius: var(--action-border-radius); + color: var(--action-success); + margin: var(--action-margin) 0; +} + +/* 快速选择 */ +.action-quick-select { + display: flex; + flex-wrap: wrap; + gap: var(--action-margin-sm); + margin-top: var(--action-margin); +} + +.action-quick-select-item { + padding: 4px var(--action-margin); + background: var(--action-background-light); + border: 1px solid var(--action-border); + border-radius: var(--action-border-radius-sm); + cursor: pointer; + font-size: 12px; + transition: var(--action-transition-fast); +} + +.action-quick-select-item:hover { + background: var(--action-primary); + border-color: var(--action-primary); + color: white; +} + +/* 帮助文本 */ +.action-help { + background: #f0f9ff; + border: 1px solid #91d5ff; + border-radius: var(--action-border-radius); + padding: var(--action-padding-sm); + margin-top: var(--action-margin); + font-size: 12px; + color: #1890ff; + line-height: 1.4; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .action-dialog { + margin: var(--action-padding); + max-width: calc(100vw - 32px); + } + + .action-options-grid.columns-3, + .action-options-grid.columns-4 { + grid-template-columns: repeat(2, 1fr); + } + + .action-dialog-footer { + flex-direction: column-reverse; + } + + .action-button { + width: 100%; + } +} + +@media (max-width: 480px) { + .action-options-grid.columns-2, + .action-options-grid.columns-3, + .action-options-grid.columns-4 { + grid-template-columns: 1fr; + } +} + +/* 动画定义 */ +@keyframes actionFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes actionFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes actionSlideIn { + from { + opacity: 0; + transform: scale(0.9) translateY(-20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +@keyframes actionSlideOut { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + to { + opacity: 0; + transform: scale(0.9) translateY(-20px); + } +} + +@keyframes actionSpin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* 滚动条样式 */ +.action-dialog-content::-webkit-scrollbar { + width: 6px; +} + +.action-dialog-content::-webkit-scrollbar-track { + background: var(--action-background-light); + border-radius: 3px; +} + +.action-dialog-content::-webkit-scrollbar-thumb { + background: var(--action-border); + border-radius: 3px; +} + +.action-dialog-content::-webkit-scrollbar-thumb:hover { + background: var(--action-text-secondary); +} \ No newline at end of file diff --git a/dashboard/src/components/actions/types.js b/dashboard/src/components/actions/types.js new file mode 100644 index 0000000..9b82598 --- /dev/null +++ b/dashboard/src/components/actions/types.js @@ -0,0 +1,479 @@ +/** + * Action组件类型定义 + * 基于T4 Action交互功能开发指南 + */ + +// Action类型枚举 +export const ActionType = { + INPUT: 'input', + EDIT: 'edit', + MULTI_INPUT: 'multiInput', + MULTI_INPUT_X: 'multiInputX', + MENU: 'menu', + SELECT: 'select', + MSGBOX: 'msgbox', + WEBVIEW: 'webview', + HELP: 'help', + // 专项动作类型(通常不包含type字段,通过其他字段识别) + SPECIAL: 'special' +} + +// 错误类型枚举 +export const ActionErrorType = { + PARSE_ERROR: 'PARSE_ERROR', + VALIDATION_ERROR: 'VALIDATION_ERROR', + NETWORK_ERROR: 'NETWORK_ERROR', + TIMEOUT_ERROR: 'TIMEOUT_ERROR', + USER_CANCEL: 'USER_CANCEL' +} + +// 输入类型枚举 +export const InputType = { + TEXT: 0, + PASSWORD: 1, + NUMBER: 2, + EMAIL: 3, + URL: 4, + FOLDER_SELECT: 5, + FILE_SELECT: 6, + DATE_SELECT: 7, + IMAGE_BASE64: 8 +} + +// 按钮类型枚举 +export const ButtonType = { + OK_CANCEL: 0, + OK_ONLY: 1, + CANCEL_ONLY: 2, + CUSTOM: 3, +} + +/** + * 规范化按钮类型值 + * 如果传入的值不存在于ButtonType枚举中,则默认返回CUSTOM + * @param {*} buttonType - 按钮类型值 + * @returns {number} 规范化后的按钮类型值 + */ +export const normalizeButtonType = (buttonType) => { + // 如果buttonType是undefined或null,使用默认值OK_CANCEL + if (buttonType === undefined || buttonType === null) { + return ButtonType.OK_CANCEL + } + + // 检查是否为有效的ButtonType值 + const validValues = Object.values(ButtonType) + if (validValues.includes(buttonType)) { + return buttonType + } + + // 如果不是有效值,默认返回CUSTOM + return ButtonType.CUSTOM +} + +/** + * 基础Action配置接口 + */ +export const createActionConfig = (config) => ({ + actionId: '', + type: ActionType.INPUT, + ...config +}) + +/** + * 单项输入Action配置 + */ +export const createInputActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.INPUT, + id: '', + title: '', + tip: '', + value: '', + msg: '', + width: 300, + button: ButtonType.OK_CANCEL, + selectData: '', + imageUrl: '', + imageHeight: 200, + imageClickCoord: false, + qrcode: '', + qrcodeSize: '200x200', + timeout: 0, + keep: false, + help: '' +}) + +/** + * 多项输入Action配置 + */ +export const createMultiInputActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.MULTI_INPUT, + title: '', + width: 400, + height: 300, + msg: '', + button: ButtonType.OK_CANCEL, + input: [] +}) + +/** + * 输入项配置 + */ +export const createInputItem = (config) => ({ + id: '', + name: '', + tip: '', + value: '', + selectData: '', + inputType: InputType.TEXT, + multiLine: 1, + validation: '', + help: '', + quickSelect: false, + onlyQuickSelect: false, + multiSelect: false, + selectWidth: 200, + selectColumn: 1, + ...config +}) + +/** + * 菜单Action配置 + */ +export const createMenuActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.MENU, + title: '', + width: 300, + column: 1, + option: [], + selectedIndex: -1 +}) + +/** + * 菜单选项配置 + */ +export const createMenuOption = (config) => ({ + name: '', + action: '', + ...config +}) + + + +/** + * 多选选项配置 + */ +export const createSelectOption = (config) => ({ + name: '', + action: '', + selected: false, + ...config +}) + +/** + * 消息弹窗Action配置 + */ +export const createMsgBoxActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.MSGBOX, + title: '', + msg: '', + htmlMsg: '', + imageUrl: '' +}) + +/** + * WebView Action配置 + */ +export const createWebViewActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.WEBVIEW, + url: '', + height: 400, + textZoom: 100 +}) + +/** + * 帮助Action配置 + */ +export const createHelpActionConfig = (config) => ({ + ...createActionConfig(config), + type: ActionType.HELP, + title: '', + content: '' +}) + +/** + * Action状态接口 + */ +export const createActionState = () => ({ + currentAction: null, + isVisible: false, + isLoading: false, + error: null, + history: [] +}) + +/** + * Action错误接口 + */ +export const createActionError = (type, message, details = null) => ({ + type, + message, + details +}) + +/** + * Action响应接口 + */ +export const createActionResponse = (config) => ({ + success: true, + data: null, + action: null, + toast: '', + error: '', + ...config +}) + +/** + * 解析Action配置 + */ +export const parseActionConfig = (data) => { + try { + if (typeof data === 'string') { + return JSON.parse(data) + } + // 如果data是一个包含config属性的对象(如action对象),则返回config + if (data && typeof data === 'object' && data.config) { + return data.config + } + // 否则直接返回data(假设它本身就是配置对象) + return data + } catch (error) { + throw createActionError( + ActionErrorType.PARSE_ERROR, + '无法解析Action配置', + error + ) + } +} + +/** + * 检测是否为专项动作 + * 专项动作通常不包含type字段,而是通过特定的字段组合来识别 + */ +export const isSpecialAction = (config) => { + if (!config || typeof config !== 'object') { + return false + } + + // 检测剪贴板操作:包含content字段且actionId为__copy__ + if (config.actionId === '__copy__' && config.content) { + return true + } + + // 检测其他专项动作模式 + // 可以根据需要添加更多专项动作的检测逻辑 + + // 检测是否有特殊的actionId前缀(如__开头的系统动作) + if (config.actionId && config.actionId.startsWith('__') && config.actionId.endsWith('__')) { + return true + } + + return false +} + +/** + * 验证专项动作配置 + */ +export const validateSpecialAction = (config) => { + // 剪贴板操作验证 + if (config.actionId === '__copy__') { + if (!config.content) { + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + '剪贴板操作必须包含content字段' + ) + } + return true + } + + // 其他专项动作的验证逻辑可以在这里添加 + // 例如:文件操作、系统调用等 + + // 对于未知的专项动作,进行基本验证 + if (config.actionId && config.actionId.startsWith('__') && config.actionId.endsWith('__')) { + // 系统动作通常只需要actionId即可 + return true + } + + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + `未知的专项动作类型: ${config.actionId}` + ) +} + +/** + * 验证Action配置 + */ +export const validateActionConfig = (config) => { + if (!config || typeof config !== 'object') { + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + 'Action配置必须是一个对象' + ) + } + + if (!config.actionId) { + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + 'actionId是必需的' + ) + } + + console.log('config:',config) + + // 检查是否为专项动作 + if (isSpecialAction(config)) { + // 专项动作有自己的验证逻辑 + return validateSpecialAction(config) + } + + // 对于普通动作,type字段是必需的 + if (!config.type) { + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + 'type字段是必需的(除非是专项动作)' + ) + } + + if (!Object.values(ActionType).includes(config.type)) { + throw createActionError( + ActionErrorType.VALIDATION_ERROR, + `不支持的Action类型: ${config.type}` + ) + } + + return true +} + +/** + * 获取快速选择数据 + */ +export const parseSelectData = (selectData) => { + if (!selectData) return [] + + try { + // 检查是否为特殊选择器(如 [folder], [calendar] 等) + if (selectData.match(/^\[(?:folder|calendar|file|image)\]$/)) { + const type = selectData.slice(1, -1).toLowerCase() + const displayNames = { + 'calendar': '📅 选择日期', + 'file': '📄 选择文件', + 'folder': '📁 选择文件夹', + 'image': '🖼️ 选择图片' + } + return [{ + name: displayNames[type] || selectData, + value: selectData + }] + } + + // 检查是否为带前缀的特殊选择器(如 [请选择字母]a,b,c,d) + if (selectData.startsWith('[') && selectData.includes(']')) { + const bracketEnd = selectData.indexOf(']') + const prefix = selectData.substring(1, bracketEnd) + const options = selectData.substring(bracketEnd + 1) + + if (options) { + // 有选项列表,解析选项 + return options.split(',').map(item => { + const trimmed = item.trim() + return { + name: trimmed, + value: trimmed + } + }).filter(item => item.name) + } else { + // 只有前缀,可能是特殊选择器 + return [{ + name: prefix, + value: selectData + }] + } + } + + // 支持标准JSON格式 + if ((selectData.startsWith('[') && selectData.endsWith(']')) || + (selectData.startsWith('{') && selectData.endsWith('}'))) { + try { + return JSON.parse(selectData) + } catch (jsonError) { + // JSON解析失败,继续其他格式处理 + } + } + + // 支持 [tag]:=[value] 格式,用逗号分隔 + if (selectData.includes(':=')) { + return selectData.split(',').map(item => { + const trimmedItem = item.trim() + const [name, value] = trimmedItem.split(':=') + return { + name: name ? name.trim() : trimmedItem, + value: value ? value.trim() : trimmedItem + } + }).filter(item => item.name) // 过滤掉空项 + } + + // 支持简单的分隔符格式(兼容旧格式) + return selectData.split('|').map(item => { + const [name, value] = item.split('=') + return { + name: name || item, + value: value || item + } + }) + } catch (error) { + console.warn('解析selectData失败:', error) + return [] + } +} + +/** + * 生成二维码URL + */ +export const generateQRCodeUrl = (text, size = '200x200') => { + const [width, height] = size.split('x').map(s => parseInt(s) || 200) + return `https://api.qrserver.com/v1/create-qr-code/?size=${width}x${height}&data=${encodeURIComponent(text)}` +} + +/** + * 防抖函数 + */ +export const debounce = (func, wait) => { + let timeout + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout) + func(...args) + } + clearTimeout(timeout) + timeout = setTimeout(later, wait) + } +} + +/** + * 节流函数 + */ +export const throttle = (func, limit) => { + let inThrottle + return function(...args) { + if (!inThrottle) { + func.apply(this, args) + inThrottle = true + setTimeout(() => inThrottle = false, limit) + } + } +} \ No newline at end of file diff --git a/dashboard/src/components/downloader/AddDownloadTaskDialog.vue b/dashboard/src/components/downloader/AddDownloadTaskDialog.vue new file mode 100644 index 0000000..7bbf6de --- /dev/null +++ b/dashboard/src/components/downloader/AddDownloadTaskDialog.vue @@ -0,0 +1,473 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/downloader/ChapterDetailsDialog.vue b/dashboard/src/components/downloader/ChapterDetailsDialog.vue new file mode 100644 index 0000000..456988f --- /dev/null +++ b/dashboard/src/components/downloader/ChapterDetailsDialog.vue @@ -0,0 +1,460 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/downloader/DownloadTaskItem.vue b/dashboard/src/components/downloader/DownloadTaskItem.vue new file mode 100644 index 0000000..95db6ab --- /dev/null +++ b/dashboard/src/components/downloader/DownloadTaskItem.vue @@ -0,0 +1,433 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/downloader/NovelDownloader.vue b/dashboard/src/components/downloader/NovelDownloader.vue new file mode 100644 index 0000000..d0ddd0a --- /dev/null +++ b/dashboard/src/components/downloader/NovelDownloader.vue @@ -0,0 +1,410 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/ArtVideoPlayer.vue b/dashboard/src/components/players/ArtVideoPlayer.vue new file mode 100644 index 0000000..32d00af --- /dev/null +++ b/dashboard/src/components/players/ArtVideoPlayer.vue @@ -0,0 +1,2884 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/DebugInfoDialog.vue b/dashboard/src/components/players/DebugInfoDialog.vue new file mode 100644 index 0000000..a0e5e00 --- /dev/null +++ b/dashboard/src/components/players/DebugInfoDialog.vue @@ -0,0 +1,616 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/EpisodeSelector.vue b/dashboard/src/components/players/EpisodeSelector.vue new file mode 100644 index 0000000..ab46c13 --- /dev/null +++ b/dashboard/src/components/players/EpisodeSelector.vue @@ -0,0 +1,433 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/LiveProxySelector.vue b/dashboard/src/components/players/LiveProxySelector.vue new file mode 100644 index 0000000..321d7c3 --- /dev/null +++ b/dashboard/src/components/players/LiveProxySelector.vue @@ -0,0 +1,227 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/PlayerHeader.vue b/dashboard/src/components/players/PlayerHeader.vue new file mode 100644 index 0000000..0d347ca --- /dev/null +++ b/dashboard/src/components/players/PlayerHeader.vue @@ -0,0 +1,732 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/SkipSettingsDialog.vue b/dashboard/src/components/players/SkipSettingsDialog.vue new file mode 100644 index 0000000..6e1091f --- /dev/null +++ b/dashboard/src/components/players/SkipSettingsDialog.vue @@ -0,0 +1,369 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/players/VideoPlayer.vue b/dashboard/src/components/players/VideoPlayer.vue new file mode 100644 index 0000000..ea51953 --- /dev/null +++ b/dashboard/src/components/players/VideoPlayer.vue @@ -0,0 +1,1191 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/BookReader.vue b/dashboard/src/components/readers/BookReader.vue new file mode 100644 index 0000000..e5a4972 --- /dev/null +++ b/dashboard/src/components/readers/BookReader.vue @@ -0,0 +1,476 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/BookmarkDialog.vue b/dashboard/src/components/readers/BookmarkDialog.vue new file mode 100644 index 0000000..e589161 --- /dev/null +++ b/dashboard/src/components/readers/BookmarkDialog.vue @@ -0,0 +1,395 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ChapterSelector.vue b/dashboard/src/components/readers/ChapterSelector.vue new file mode 100644 index 0000000..a1c7db4 --- /dev/null +++ b/dashboard/src/components/readers/ChapterSelector.vue @@ -0,0 +1,471 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ComicReader.vue b/dashboard/src/components/readers/ComicReader.vue new file mode 100644 index 0000000..64cd20d --- /dev/null +++ b/dashboard/src/components/readers/ComicReader.vue @@ -0,0 +1,802 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ComicReaderHeader.vue b/dashboard/src/components/readers/ComicReaderHeader.vue new file mode 100644 index 0000000..57e0530 --- /dev/null +++ b/dashboard/src/components/readers/ComicReaderHeader.vue @@ -0,0 +1,508 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ComicSettingsDialog.vue b/dashboard/src/components/readers/ComicSettingsDialog.vue new file mode 100644 index 0000000..a97ac41 --- /dev/null +++ b/dashboard/src/components/readers/ComicSettingsDialog.vue @@ -0,0 +1,486 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ReaderHeader.vue b/dashboard/src/components/readers/ReaderHeader.vue new file mode 100644 index 0000000..7232d6e --- /dev/null +++ b/dashboard/src/components/readers/ReaderHeader.vue @@ -0,0 +1,452 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/readers/ReadingSettingsDialog.vue b/dashboard/src/components/readers/ReadingSettingsDialog.vue new file mode 100644 index 0000000..95ff089 --- /dev/null +++ b/dashboard/src/components/readers/ReadingSettingsDialog.vue @@ -0,0 +1,617 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/composables/useSkipSettings.js b/dashboard/src/composables/useSkipSettings.js new file mode 100644 index 0000000..fa44eab --- /dev/null +++ b/dashboard/src/composables/useSkipSettings.js @@ -0,0 +1,360 @@ +import { ref, computed, onUnmounted } from 'vue' + +/** + * 片头片尾跳过功能的组合式函数 + * @param {Object} options - 配置选项 + * @param {Function} options.onSkipToNext - 跳转到下一集的回调函数(仅用于片尾跳过到下一集的场景) + * @param {Function} options.getCurrentTime - 获取当前播放时间的函数 + * @param {Function} options.setCurrentTime - 设置播放时间的函数 + * @param {Function} options.getDuration - 获取视频总时长的函数 + * @returns {Object} 返回片头片尾跳过相关的响应式数据和方法 + */ +export function useSkipSettings(options = {}) { + const { + onSkipToNext = () => {}, + getCurrentTime = () => 0, + setCurrentTime = () => {}, + getDuration = () => 0 + } = options + + // 响应式数据 + const showSkipSettingsDialog = ref(false) + const skipIntroEnabled = ref(false) + const skipOutroEnabled = ref(false) + const skipIntroSeconds = ref(90) + const skipOutroSeconds = ref(90) + const skipIntroApplied = ref(false) + const skipOutroApplied = ref(false) + const skipOutroTimer = ref(null) + const lastSkipTime = ref(0) // 记录上次跳过的时间戳,用于防抖 + + //// 用户交互状态 + const userSeeking = ref(false) + const lastUserSeekTime = ref(0) + + // 全屏状态跟踪 + const isFullscreenChanging = ref(false) + const lastFullscreenChangeTime = ref(0) // 上次用户拖动的时间戳 + + // 计算属性 + const skipEnabled = computed(() => { + return skipIntroEnabled.value || skipOutroEnabled.value + }) + + // 本地存储键名 + const STORAGE_KEY = 'drplayer_skip_settings' + + /** + * 从本地存储加载设置 + */ + const loadSkipSettings = () => { + try { + const saved = localStorage.getItem(STORAGE_KEY) + console.log('从 localStorage 加载设置:', saved) + if (saved) { + const settings = JSON.parse(saved) + skipIntroEnabled.value = settings.skipIntroEnabled || false + skipOutroEnabled.value = settings.skipOutroEnabled || false + skipIntroSeconds.value = settings.skipIntroSeconds || 90 + skipOutroSeconds.value = settings.skipOutroSeconds || 90 + console.log('加载的设置:', { + skipIntroEnabled: skipIntroEnabled.value, + skipOutroEnabled: skipOutroEnabled.value, + skipIntroSeconds: skipIntroSeconds.value, + skipOutroSeconds: skipOutroSeconds.value + }) + } else { + console.log('没有找到保存的设置,使用默认值') + } + } catch (error) { + console.warn('加载片头片尾设置失败:', error) + } + } + + /** + * 保存设置到本地存储 + */ + const saveSkipSettings = (settings) => { + try { + // 更新响应式数据 + skipIntroEnabled.value = settings.skipIntroEnabled + skipOutroEnabled.value = settings.skipOutroEnabled + skipIntroSeconds.value = settings.skipIntroSeconds + skipOutroSeconds.value = settings.skipOutroSeconds + + // 保存到本地存储 + const settingsToSave = { + skipIntroEnabled: settings.skipIntroEnabled, + skipOutroEnabled: settings.skipOutroEnabled, + skipIntroSeconds: settings.skipIntroSeconds, + skipOutroSeconds: settings.skipOutroSeconds + } + localStorage.setItem(STORAGE_KEY, JSON.stringify(settingsToSave)) + + // 应用新设置 + applySkipSettings() + } catch (error) { + console.warn('保存片头片尾设置失败:', error) + } + } + + /** + * 立即应用片头跳过逻辑(用于视频刚开始播放时) + */ + const applyIntroSkipImmediate = () => { + if (!skipIntroEnabled.value || skipIntroApplied.value) { + return false + } + + const currentTime = getCurrentTime() + const now = Date.now() + + // 检查用户是否正在拖动或刚刚拖动过(3秒内) + if (userSeeking.value || (lastUserSeekTime.value > 0 && now - lastUserSeekTime.value < 3000)) { + return false + } + + // 检查是否正在全屏切换或刚刚切换过(2秒内) + if (isFullscreenChanging.value || (lastFullscreenChangeTime.value > 0 && now - lastFullscreenChangeTime.value < 2000)) { + return false + } + + // 立即跳过模式:如果当前时间很小(小于等于1秒)且在片头跳过范围内,立即跳过 + if (currentTime <= 1 && currentTime <= skipIntroSeconds.value) { + console.log(`立即跳过片头:从 ${currentTime.toFixed(1)}s 跳转到 ${skipIntroSeconds.value}s`) + setCurrentTime(skipIntroSeconds.value) + skipIntroApplied.value = true + lastSkipTime.value = now + return true // 返回 true 表示已执行跳过 + } + + return false // 返回 false 表示未执行跳过 + } + + /** + * 应用片头跳过逻辑 + */ + const applyIntroSkip = () => { + if (!skipIntroEnabled.value || skipIntroApplied.value) { + return + } + + const currentTime = getCurrentTime() + const now = Date.now() + + // 检查用户是否正在拖动或刚刚拖动过(3秒内) + if (userSeeking.value || (lastUserSeekTime.value > 0 && now - lastUserSeekTime.value < 3000)) { + return + } + + // 检查是否正在全屏切换或刚刚切换过(2秒内) + if (isFullscreenChanging.value || (lastFullscreenChangeTime.value > 0 && now - lastFullscreenChangeTime.value < 2000)) { + return + } + + // 防抖:如果距离上次跳过不足1秒,则忽略(但如果是新视频,lastSkipTime为0,允许跳过) + if (lastSkipTime.value > 0 && now - lastSkipTime.value < 1000) { + return + } + + // 如果当前时间在片头跳过范围内,则跳过 + if (currentTime <= skipIntroSeconds.value) { + console.log(`已跳过片头:从 ${currentTime.toFixed(1)}s 跳转到 ${skipIntroSeconds.value}s`) + setCurrentTime(skipIntroSeconds.value) + skipIntroApplied.value = true + lastSkipTime.value = now + } + } + + /** + * 应用片尾跳过逻辑 + */ + const applyOutroSkip = () => { + if (!skipOutroEnabled.value || skipOutroApplied.value) return + + const duration = getDuration() + if (duration <= 0) return + + const currentTime = getCurrentTime() + const timeToSkip = duration - skipOutroSeconds.value + const now = Date.now() + + // 防抖:如果距离上次跳过不足2秒,则忽略 + if (now - lastSkipTime.value < 2000) { + return + } + + // 当播放时间达到片尾跳过点时,直接跳转到视频结束位置 + if (currentTime >= timeToSkip && currentTime < duration - 1) { + setCurrentTime(duration - 0.1) // 跳转到接近结束的位置,触发 ended 事件 + skipOutroApplied.value = true + lastSkipTime.value = now + console.log(`已跳过片尾 ${skipOutroSeconds.value} 秒,跳转到视频结束位置`) + } + } + + /** + * 应用片头片尾设置 + */ + const applySkipSettings = () => { + // 优先尝试立即片头跳过(用于视频刚开始播放时) + const immediateSkipped = applyIntroSkipImmediate() + + // 如果立即跳过未执行,则使用常规片头跳过 + if (!immediateSkipped) { + applyIntroSkip() + } + + // 应用片尾跳过 + applyOutroSkip() + } + + // 防抖变量 + let timeUpdateDebounceTimer = null + + /** + * 处理时间更新事件 + */ + const handleTimeUpdate = () => { + // 防抖:减少频繁调用,每200ms最多执行一次 + if (timeUpdateDebounceTimer) { + clearTimeout(timeUpdateDebounceTimer) + } + + timeUpdateDebounceTimer = setTimeout(() => { + // 应用片头跳过(仅在未应用时) + if (skipIntroEnabled.value && !skipIntroApplied.value) { + applyIntroSkip() + } + + // 应用片尾跳过(仅在未应用时) + if (skipOutroEnabled.value && !skipOutroApplied.value) { + applyOutroSkip() + } + }, 200) + } + + /** + * 重置跳过状态 + */ + const resetSkipState = () => { + skipIntroApplied.value = false + skipOutroApplied.value = false + lastSkipTime.value = 0 // 重置防抖时间戳 + + // 重置用户交互状态 + userSeeking.value = false + lastUserSeekTime.value = 0 + + // 重置全屏状态 + isFullscreenChanging.value = false + lastFullscreenChangeTime.value = 0 + + // 清除片尾跳过定时器(如果存在) + if (skipOutroTimer.value) { + clearTimeout(skipOutroTimer.value) + skipOutroTimer.value = null + } + } + + /** + * 用户开始拖动进度条 + */ + const onUserSeekStart = () => { + userSeeking.value = true + console.log('用户开始拖动进度条') + } + + /** + * 用户结束拖动进度条 + */ + const onUserSeekEnd = () => { + userSeeking.value = false + lastUserSeekTime.value = Date.now() + console.log('用户结束拖动进度条') + } + + /** + * 全屏状态开始变化 + */ + const onFullscreenChangeStart = () => { + isFullscreenChanging.value = true + console.log('全屏状态开始变化') + } + + /** + * 全屏状态变化结束 + */ + const onFullscreenChangeEnd = () => { + isFullscreenChanging.value = false + lastFullscreenChangeTime.value = Date.now() + console.log('全屏状态变化结束') + } + + /** + * 初始化片头片尾设置 + */ + const initSkipSettings = () => { + resetSkipState() + loadSkipSettings() + } + + /** + * 打开设置弹窗 + */ + const openSkipSettingsDialog = () => { + showSkipSettingsDialog.value = true + } + + /** + * 关闭设置弹窗 + */ + const closeSkipSettingsDialog = () => { + showSkipSettingsDialog.value = false + } + + /** + * 清理资源 + */ + const cleanup = () => { + if (skipOutroTimer.value) { + clearTimeout(skipOutroTimer.value) + skipOutroTimer.value = null + } + } + + // 组件卸载时清理资源 + onUnmounted(() => { + cleanup() + }) + + return { + // 响应式数据 + showSkipSettingsDialog, + skipIntroEnabled, + skipOutroEnabled, + skipIntroSeconds, + skipOutroSeconds, + skipIntroApplied, + skipOutroTimer, + + // 计算属性 + skipEnabled, + + // 方法 + loadSkipSettings, + saveSkipSettings, + applySkipSettings, + applyIntroSkipImmediate, + handleTimeUpdate, + resetSkipState, + initSkipSettings, + openSkipSettingsDialog, + closeSkipSettingsDialog, + cleanup, + onUserSeekStart, + onUserSeekEnd, + onFullscreenChangeStart, + onFullscreenChangeEnd + } +} \ No newline at end of file diff --git a/dashboard/src/demo/action-demo.js b/dashboard/src/demo/action-demo.js new file mode 100644 index 0000000..7746b5b --- /dev/null +++ b/dashboard/src/demo/action-demo.js @@ -0,0 +1,399 @@ +// Action组件功能演示脚本 +// 这个脚本展示了如何使用Action组件系统的各种功能 + +import { Actions } from '@/components/actions' + +// 演示基础输入功能 +export const demoBasicInput = async () => { + try { + const result = await Actions.input({ + actionId: 'demo-basic-input', + title: '用户信息输入', + message: '请输入您的姓名:', + placeholder: '请输入姓名', + required: true, + timeout: 30000 + }) + console.log('输入结果:', result) + return result + } catch (error) { + console.error('输入取消或超时:', error) + } +} + +// 演示多项输入功能 +export const demoMultiInput = async () => { + try { + const result = await Actions.multiInput({ + actionId: 'demo-multi-input', + title: '用户注册信息', + message: '请填写注册信息:', + inputs: [ + { + key: 'username', + label: '用户名', + placeholder: '请输入用户名', + required: true, + validation: { + minLength: 3, + maxLength: 20, + pattern: '^[a-zA-Z0-9_]+$' + } + }, + { + key: 'email', + label: '邮箱', + placeholder: '请输入邮箱地址', + required: true, + validation: { + type: 'email' + } + }, + { + key: 'password', + label: '密码', + type: 'password', + placeholder: '请输入密码', + required: true, + validation: { + minLength: 6 + } + }, + { + key: 'bio', + label: '个人简介', + type: 'textarea', + placeholder: '请输入个人简介(可选)', + required: false + } + ], + timeout: 60000 + }) + console.log('多项输入结果:', result) + return result + } catch (error) { + console.error('多项输入取消或超时:', error) + } +} + +// 演示菜单选择功能 +export const demoMenu = async () => { + try { + const result = await Actions.menu({ + actionId: 'demo-menu', + title: '选择操作', + message: '请选择您要执行的操作:', + options: [ + { + key: 'create', + title: '创建新项目', + description: '创建一个新的项目', + icon: '➕' + }, + { + key: 'edit', + title: '编辑项目', + description: '编辑现有项目', + icon: '✏️' + }, + { + key: 'delete', + title: '删除项目', + description: '删除选中的项目', + icon: '🗑️' + }, + { + key: 'export', + title: '导出数据', + description: '导出项目数据', + icon: '📤' + } + ], + multiple: false, + searchable: true, + timeout: 30000 + }) + console.log('菜单选择结果:', result) + return result + } catch (error) { + console.error('菜单选择取消或超时:', error) + } +} + +// 演示下拉选择功能 +export const demoSelect = async () => { + try { + const result = await Actions.select({ + actionId: 'demo-select', + title: '选择技能', + message: '请选择您掌握的技能(可多选):', + options: [ + { key: 'javascript', title: 'JavaScript' }, + { key: 'vue', title: 'Vue.js' }, + { key: 'react', title: 'React' }, + { key: 'angular', title: 'Angular' }, + { key: 'nodejs', title: 'Node.js' }, + { key: 'python', title: 'Python' }, + { key: 'java', title: 'Java' }, + { key: 'csharp', title: 'C#' }, + { key: 'php', title: 'PHP' }, + { key: 'go', title: 'Go' } + ], + multiple: true, + searchable: true, + defaultSelected: ['javascript', 'vue'], + timeout: 45000 + }) + console.log('下拉选择结果:', result) + return result + } catch (error) { + console.error('下拉选择取消或超时:', error) + } +} + +// 演示消息框功能 +export const demoMsgBox = async () => { + try { + // 信息提示 + await Actions.info({ + title: '信息提示', + message: '这是一个信息提示消息', + details: '详细信息:操作已成功完成。', + timeout: 5000 + }) + + // 成功提示 + await Actions.success({ + title: '操作成功', + message: '数据保存成功!', + timeout: 3000 + }) + + // 警告提示 + await Actions.warning({ + title: '警告', + message: '请注意:此操作可能会影响系统性能。', + timeout: 5000 + }) + + // 错误提示 + await Actions.error({ + title: '错误', + message: '操作失败,请重试。', + details: '错误代码:500\n错误信息:服务器内部错误', + timeout: 8000 + }) + + // 确认对话框 + const confirmed = await Actions.confirm({ + title: '确认删除', + message: '您确定要删除这个项目吗?', + details: '删除后将无法恢复,请谨慎操作。', + timeout: 15000 + }) + console.log('确认结果:', confirmed) + + return confirmed + } catch (error) { + console.error('消息框操作取消或超时:', error) + } +} + +// 演示进度条功能 +export const demoProgress = async () => { + try { + const progressAction = Actions.progress({ + title: '文件上传中', + message: '正在上传文件,请稍候...', + progress: 0, + timeout: 30000 + }) + + // 模拟进度更新 + for (let i = 0; i <= 100; i += 10) { + await new Promise(resolve => setTimeout(resolve, 500)) + progressAction.updateProgress(i, `上传进度: ${i}%`) + } + + await progressAction.complete('上传完成!') + console.log('进度条演示完成') + } catch (error) { + console.error('进度条操作取消或超时:', error) + } +} + +// 演示网页视图功能 +export const demoWebView = async () => { + try { + const result = await Actions.webView({ + actionId: 'demo-webview', + title: '网页浏览', + url: 'https://www.example.com', + width: '80%', + height: '70%', + showToolbar: true, + allowFullscreen: true, + timeout: 60000 + }) + console.log('网页视图结果:', result) + return result + } catch (error) { + console.error('网页视图取消或超时:', error) + } +} + +// 演示帮助页面功能 +export const demoHelp = async () => { + try { + const result = await Actions.help({ + actionId: 'demo-help', + title: 'Action组件使用帮助', + message: '欢迎使用Action组件系统!', + details: ` +## Action组件系统 + +Action组件系统是一个强大的交互式UI组件库,支持多种类型的用户交互。 + +### 主要功能 + +1. **输入组件** - 单项和多项输入 +2. **选择组件** - 菜单和下拉选择 +3. **消息组件** - 各种类型的消息提示 +4. **网页组件** - 内嵌网页浏览 +5. **帮助组件** - 帮助信息展示 + +### 使用方法 + +\`\`\`javascript +import { Actions } from '@/components/actions' + +// 显示输入框 +const result = await Actions.input({ + title: '输入标题', + message: '请输入内容', + placeholder: '占位符文本' +}) +\`\`\` + `, + steps: [ + '导入Actions模块', + '调用相应的Action方法', + '配置Action参数', + '处理返回结果' + ], + faq: [ + { + question: '如何设置超时时间?', + answer: '在Action配置中添加timeout参数,单位为毫秒。' + }, + { + question: '如何处理用户取消操作?', + answer: 'Action方法会抛出异常,可以使用try-catch捕获。' + } + ], + links: [ + { + title: 'Vue.js官方文档', + url: 'https://vuejs.org/' + }, + { + title: 'Arco Design Vue', + url: 'https://arco.design/vue' + } + ], + contact: { + email: 'support@example.com', + phone: '400-123-4567', + website: 'https://www.example.com' + }, + timeout: 120000 + }) + console.log('帮助页面结果:', result) + return result + } catch (error) { + console.error('帮助页面取消或超时:', error) + } +} + +// 演示队列功能 +export const demoQueue = async () => { + try { + console.log('开始队列演示...') + + // 同时显示多个Action(如果允许) + const promises = [ + Actions.alert({ + title: '队列测试 1', + message: '这是第一个Action', + timeout: 5000 + }), + Actions.alert({ + title: '队列测试 2', + message: '这是第二个Action', + timeout: 5000 + }), + Actions.alert({ + title: '队列测试 3', + message: '这是第三个Action', + timeout: 5000 + }) + ] + + await Promise.all(promises) + console.log('队列演示完成') + } catch (error) { + console.error('队列演示出错:', error) + } +} + +// 综合演示 +export const demoAll = async () => { + console.log('开始综合演示...') + + try { + // 1. 基础输入 + console.log('1. 基础输入演示') + await demoBasicInput() + + // 2. 多项输入 + console.log('2. 多项输入演示') + await demoMultiInput() + + // 3. 菜单选择 + console.log('3. 菜单选择演示') + await demoMenu() + + // 4. 下拉选择 + console.log('4. 下拉选择演示') + await demoSelect() + + // 5. 消息框 + console.log('5. 消息框演示') + await demoMsgBox() + + // 6. 进度条 + console.log('6. 进度条演示') + await demoProgress() + + // 7. 帮助页面 + console.log('7. 帮助页面演示') + await demoHelp() + + console.log('综合演示完成!') + } catch (error) { + console.error('综合演示出错:', error) + } +} + +export default { + demoBasicInput, + demoMultiInput, + demoMenu, + demoSelect, + demoMsgBox, + demoProgress, + demoWebView, + demoHelp, + demoQueue, + demoAll +} \ No newline at end of file diff --git a/dashboard/src/main.js b/dashboard/src/main.js index 05f6191..fe425ce 100644 --- a/dashboard/src/main.js +++ b/dashboard/src/main.js @@ -1,21 +1,49 @@ import {createApp} from 'vue' -import PrimeVue from 'primevue/config'; -import Aura from '@primevue/themes/aura'; -import './style.css' -import 'primeflex/primeflex.css' -import 'primeflex/themes/primeone-light.css' // 亮系主题 -// import 'primeflex/themes/primeone-dark.css' // 暗系主题 -import 'primeicons/primeicons.css'; // 图标 -import router from './router'; // 引入路由 +// import './style.css' import App from './App.vue' -import { createPinia } from 'pinia'; +import router from './router' // 引入路由 +import ArcoVue from '@arco-design/web-vue' +import ArcoVueIcon from '@arco-design/web-vue/es/icon'; +import '@arco-design/web-vue/dist/arco.css' +import '@/assets/icon_font/iconfont.css' // 引入iconfont样式 +import {createPinia} from 'pinia' +import 'viewerjs/dist/viewer.css' +import VueViewer from 'v-viewer' +import ECharts from 'vue-echarts' +import ActionComponents from '@/components/actions' +import { use } from 'echarts/core' +import { + CanvasRenderer +} from 'echarts/renderers' +import { + BarChart, + LineChart, + PieChart +} from 'echarts/charts' +import { + GridComponent, + TooltipComponent, + LegendComponent, + TitleComponent +} from 'echarts/components' -const app = createApp(App); -app.use(PrimeVue, { - theme: { - preset: Aura - } -}); +use([ + CanvasRenderer, + BarChart, + LineChart, + PieChart, + GridComponent, + TooltipComponent, + LegendComponent, + TitleComponent +]) + +const app = createApp(App) app.use(router) +app.use(ArcoVue); +app.use(ArcoVueIcon); app.use(createPinia()) -app.mount('#app') +app.use(VueViewer) +app.use(ActionComponents) +app.component('v-chart', ECharts) +app.mount('#app') \ No newline at end of file diff --git a/dashboard/src/mock/data.json b/dashboard/src/mock/data.json new file mode 100644 index 0000000..10d1df8 --- /dev/null +++ b/dashboard/src/mock/data.json @@ -0,0 +1,2934 @@ +{ + "cost": 175, + "sites_count": 162, + "wallpaper": "https://api.likepoems.com/img/bing/", + "spider": "../public/jar/custom_spider.jar;md5;cc3b3d2ddf389338b62eccd4a938b610", + "homepage": "https://github.com/hjdhnx/drpy-node", + "aiLogo": "https://image.baidu.com/search/index?tn=baiduimage&ct=201326592&lm=-1&cl=2&word=%E7%8B%97%E4%BD%9C%E4%B8%BA%E8%BD%AF%E4%BB%B6%E5%9B%BE%E6%A0%87&dyTabStr=MCwxMiwzLDEsMiwxMyw3LDYsNSw5", + "homeLogo": "https://github.catvod.com/https://raw.githubusercontent.com/hjdhnx/drpy-node/refs/heads/main/public/images/logo500x200-1.png", + "sniffer": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27", + "isVideoFormat": "http((?!http).){26,}\\.(m3u8|mp4|flv|avi|mkv|wmv|mpg|mpeg|mov|ts|3gp|rm|rmvb|asf|m4a|mp3|wma)", + "custom": [ + { + "url": "/Cloud/Down/AliCloud/", + "mimeType": "text/html", + "encoding": "utf-8", + "header": { + "Referer": "https://zxzj.vip/" + } + }, + { + "url": "ysting.ysxs8.vip", + "mimeType": "text/html", + "encoding": "utf-8", + "header": { + "Referer": "http://ysting.ysxs8.vip:81" + } + } + ] + }, + "recommend": [ + { + "name": "豆瓣推荐", + "request": { + "method": "GET", + "header": [ + { + "key": "Referer", + "value": "https://movie.douban.com/" + } + ], + "url": { + "raw": "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&playable=1&start=0&year_range=2022,2022" + } + }, + "response": { + "result": "$.data", + "data": [ + { + "key": "name", + "value": "title" + }, + { + "key": "note", + "value": "rate" + }, + { + "key": "pic", + "value": "cover" + } + ] + }, + "expires": "86400" + } + ], + "rating": [ + { + "name": "rating", + "request": { + "method": "GET", + "url": { + "raw": "https://api.wmdb.tv/api/v1/movie/search?q={name}&limit=1" + } + }, + "response": { + "result": "this", + "data": [ + { + "key": "rating", + "value": "doubanRating" + } + ] + } + } + ], + "doh": [ + { + "ips": [ + "8.8.4.4", + "8.8.8.8" + ], + "name": "Google", + "url": "https://dns.google/dns-query" + }, + { + "ips": [ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::1111", + "2606:4700:4700::1001" + ], + "name": "Cloudflare", + "url": "https://cloudflare-dns.com/dns-query" + }, + { + "ips": [ + "94.140.14.140", + "94.140.14.141" + ], + "name": "AdGuard", + "url": "https://dns.adguard.com/dns-query" + }, + { + "ips": [ + "84.200.69.80", + "84.200.70.40" + ], + "name": "DNSWatch", + "url": "https://resolver2.dns.watch/dns-query" + }, + { + "ips": [ + "9.9.9.9", + "149.112.112.112" + ], + "name": "Quad9", + "url": "https://dns.quad9.net/dns-quer" + } + ], + "ijk": [ + { + "group": "软解码", + "options": [ + { + "category": 4, + "name": "opensles", + "value": "0" + }, + { + "category": 4, + "name": "overlay-format", + "value": "842225234" + }, + { + "category": 4, + "name": "framedrop", + "value": "1" + }, + { + "category": 4, + "name": "soundtouch", + "value": "1" + }, + { + "category": 4, + "name": "start-on-prepared", + "value": "1" + }, + { + "category": 1, + "name": "http-detect-range-support", + "value": "0" + }, + { + "category": 1, + "name": "fflags", + "value": "fastseek" + }, + { + "category": 2, + "name": "skip_loop_filter", + "value": "48" + }, + { + "category": 4, + "name": "reconnect", + "value": "1" + }, + { + "category": 4, + "name": "enable-accurate-seek", + "value": "0" + }, + { + "category": 4, + "name": "mediacodec", + "value": "0" + }, + { + "category": 4, + "name": "mediacodec-auto-rotate", + "value": "0" + }, + { + "category": 4, + "name": "mediacodec-handle-resolution-change", + "value": "0" + }, + { + "category": 4, + "name": "mediacodec-hevc", + "value": "0" + }, + { + "category": 1, + "name": "dns_cache_timeout", + "value": "600000000" + }, + { + "category": 4, + "name": "max-buffer-size", + "value": "15728640" + } + ] + }, + { + "group": "硬解码", + "options": [ + { + "category": 4, + "name": "opensles", + "value": "0" + }, + { + "category": 4, + "name": "overlay-format", + "value": "842225234" + }, + { + "category": 4, + "name": "framedrop", + "value": "1" + }, + { + "category": 4, + "name": "soundtouch", + "value": "1" + }, + { + "category": 4, + "name": "start-on-prepared", + "value": "1" + }, + { + "category": 1, + "name": "http-detect-range-support", + "value": "0" + }, + { + "category": 1, + "name": "fflags", + "value": "fastseek" + }, + { + "category": 2, + "name": "skip_loop_filter", + "value": "48" + }, + { + "category": 4, + "name": "reconnect", + "value": "1" + }, + { + "category": 4, + "name": "max-buffer-size", + "value": "15728640" + }, + { + "category": 4, + "name": "enable-accurate-seek", + "value": "0" + }, + { + "category": 4, + "name": "mediacodec", + "value": "1" + }, + { + "category": 4, + "name": "mediacodec-auto-rotate", + "value": "1" + }, + { + "category": 4, + "name": "mediacodec-handle-resolution-change", + "value": "1" + }, + { + "category": 4, + "name": "mediacodec-hevc", + "value": "1" + }, + { + "category": 1, + "name": "dns_cache_timeout", + "value": "600000000" + } + ] + } + ], + "rules": [ + { + "hosts": [ + "raw.githubusercontent.com", + "googlevideo.com", + "cdn.v82u1l.com", + "cdn.iz8qkg.com", + "cdn.kin6c1.com", + "c.biggggg.com", + "c.olddddd.com", + "haiwaikan.com", + "www.histar.tv", + "youtube.com", + "uhibo.com", + ".*boku.*", + ".*nivod.*", + ".*ulivetv.*", + "wogg.link", + "wogg.xyz" + ], + "name": "proxy" + }, + { + "hosts": [ + "magnet" + ], + "name": "cl", + "regex": [ + "最新", + "直播", + "更新" + ] + }, + { + "hosts": [ + "haiwaikan" + ], + "name": "海外看", + "regex": [ + "10.0099", + "10.3333", + "16.0599", + "8.1748", + "12.33", + "10.85" + ] + }, + { + "hosts": [ + "suonizy" + ], + "name": "索尼", + "regex": [ + "15.1666", + "15.2666" + ] + }, + { + "hosts": [ + "bfzy", + "bfbfvip" + ], + "name": "暴風", + "regex": [ + "#EXTINF.*?\\s+.*?adjump.*?\\.ts\\s+" + ] + }, + { + "hosts": [ + "aws.ulivetv.net" + ], + "name": "星星", + "regex": [ + "#EXT-X-DISCONTINUITY\\r*\\n*#EXTINF:8,[\\s\\S]*?#EXT-X-DISCONTINUITY" + ] + }, + { + "hosts": [ + "vip.lz", + "hd.lz", + "v.cdnlz" + ], + "name": "量子", + "regex": [ + "18.5333" + ] + }, + { + "hosts": [ + "vip.ffzy", + "hd.ffzy" + ], + "name": "非凡", + "regex": [ + "25.1999", + "25.0666", + "25.08", + "20.52" + ] + }, + { + "hosts": [ + "huoshan.com" + ], + "name": "火山嗅探", + "regex": [ + "item_id=" + ] + }, + { + "hosts": [ + "douyin.com" + ], + "name": "抖音嗅探", + "regex": [ + "is_play_url=" + ] + }, + { + "hosts": [ + "api.52wyb.com" + ], + "name": "七新嗅探", + "regex": [ + "m3u8?pt=m3u8" + ] + }, + { + "hosts": [ + "10086.cn" + ], + "name": "czzy", + "regex": [ + "/storageWeb/servlet/downloadServlet" + ] + }, + { + "exclude": [ + ".m3u8" + ], + "hosts": [ + "bytetos.com", + "byteimg.com", + "bytednsdoc.com", + "pstatp.com" + ], + "name": "bdys", + "regex": [ + "/tos-cn" + ] + }, + { + "exclude": [ + ".m3u8" + ], + "hosts": [ + "bdys10.com" + ], + "name": "bdys10", + "regex": [ + "/obj/" + ] + } + ], + "ads": [ + "mimg.0c1q0l.cn", + "www.googletagmanager.com", + "www.google-analytics.com", + "mc.usihnbcq.cn", + "mg.g1mm3d.cn", + "mscs.svaeuzh.cn", + "cnzz.hhttm.top", + "tp.vinuxhome.com", + "cnzz.mmstat.com", + "www.baihuillq.com", + "s23.cnzz.com", + "z3.cnzz.com", + "c.cnzz.com", + "stj.v1vo.top", + "z12.cnzz.com", + "img.mosflower.cn", + "tips.gamevvip.com", + "ehwe.yhdtns.com", + "xdn.cqqc3.com", + "www.jixunkyy.cn", + "sp.chemacid.cn", + "hm.baidu.com", + "s9.cnzz.com", + "z6.cnzz.com", + "um.cavuc.com", + "mav.mavuz.com", + "wofwk.aoidf3.com", + "z5.cnzz.com", + "xc.hubeijieshikj.cn", + "tj.tianwenhu.com", + "xg.gars57.cn", + "k.jinxiuzhilv.com", + "cdn.bootcss.com", + "ppl.xunzhuo123.com", + "xomk.jiangjunmh.top", + "img.xunzhuo123.com", + "z1.cnzz.com", + "s13.cnzz.com", + "xg.huataisangao.cn", + "z7.cnzz.com", + "xg.huataisangao.cn", + "z2.cnzz.com", + "s96.cnzz.com", + "q11.cnzz.com", + "thy.dacedsfa.cn", + "xg.whsbpw.cn", + "s19.cnzz.com", + "z8.cnzz.com", + "s4.cnzz.com", + "f5w.as12df.top", + "ae01.alicdn.com", + "www.92424.cn", + "k.wudejia.com", + "vivovip.mmszxc.top", + "qiu.xixiqiu.com", + "cdnjs.hnfenxun.com", + "cms.qdwght.com", + "wan.51img1.com", + "iqiyi.hbuioo.com", + "vip.ffzyad.com", + "https://lf1-cdn-tos.bytegoofy.com/obj/tos-cn-i-dy/455ccf9e8ae744378118e4bd289288dd" + ], + "drives": [ + { + "name": "阿里", + "password": "43886374072944a2bcc55a0ed129ab48", + "type": "alidrive" + } + ], + "sites": [ + { + "key": "drpyS_设置中心", + "name": "设置中心(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/设置中心?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "设置中心", + "logo": "https://avatars.githubusercontent.com/u/49803097?v=4", + "more": { + "sourceTag": "设置,动作", + "actions": [ + { + "name": "推送", + "action": "{'actionId':'推送视频播放','id':'push','type':'input','title':'推送视频地址进行播放','tip':'支持网盘、官链、直链、待嗅探链接','value':'','msg':'请输入待推送的视频地址','imageUrl':'https://github.catvod.com/https://raw.githubusercontent.com/hjdhnx/drpy-node/refs/heads/main/public/images/lives.jpg','imageHeight':200,'imageType':'card_pic_3','keep':true,'button':4,'width':640,'selectData':'123:=https://www.123684.com/s/oec7Vv-DggWh?ZY4K,腾讯:=https://v.qq.com/x/cover/mzc00200vkqr54u/u4100l66fas.html,爱奇艺:=http://www.iqiyi.com/v_1b0tk1b8tl8.html,夸克:=https://pan.quark.cn/s/6c8158e258f3,UC:=https://drive.uc.cn/s/59023f57d3ce4?public=1,阿里:=https://www.alipan.com/s/vgXMcowK8pQ,天翼:=https://cloud.189.cn/web/share?code=INJbU3NbqyUj,移动1:=https://yun.139.com/shareweb/#/w/i/0i5CLQ7BpV7Ai,移动2:=https://caiyun.139.com/m/i?2jexC1gcjeN7q,移动3:=https://yun.139.com/shareweb/#/w/i/2i2MoE9ZHn9p1,直链1:=https://vdse.bdstatic.com//628ca08719cef5987ea2ae3c6f0d2386.mp4,嗅探1:=https://www.6080kk.cc/haokanplay/178120-1-1.html,嗅探2:=https://www.hahads.com/play/537106-3-1.html,多集:=https://v.qq.com/x/cover/m441e3rjq9kwpsc/m00253deqqo.html#https://pan.quark.cn/s/6c8158e258f3,海阔二级单线路:=H4sIAAAAAAAAA52Uy27TQBSGXwUZlsT2GefadZ+AN3ATk7qKL7guUoKQXAQFeoEG6oKaVBUFlBZFbdQ0TXAIeRjPTJwVr8AYCsNyijQbnzPfPz72p3kk6WXf8aQFibzszFsb0l2p7Ni+YfusFAe78/W383C6eC8OmnEQsEVal7NxiEebeLQ/i75oKvl6iccfZwdPWY0OhnR8+uPbdnJ2kUx7ONrAo094skMOD+ZHHbL1nIbHbCf53KdBh7RPaP+Yfm8n5x+S3gWr016TtCb03VUa2Brh6A0Nm8ngVRysk7Nt+mI3aYfk9fs0YfMERxENn+FoKw6e3KJ7V8lgyF6+YnrG9UAPTLu6ZNgrpu4ZNlJRlrXve47FWrNomgzPEdJYydYtIx1/Z0rbXTzps9zrza5ZZo1l33dXFxSFPWlyvdGom5ZeNVblsmMpa27N0SvKQ6eipEwGIINAgYKGIA+lYg7kFbfKkta8Wnpqt6sC+8Z3/kQuyXm1qDZ+RbEMt6bXFVBBQ6UMy5KXfat2O4WQMIQ4pAlDGoeywlCWQzlhKMehvDCU51BBGCpwqCgMFTlUEoZKfyFQxX+uyqkbKMGdAHEnAP0Xxa0AcZWAawHiLgH3AsRlAi4GiNsE3AwQ1wm4GiDuE/zjhrhQiLuBxI1C3A0kbhTibqAb3DK/3ZAe/wSSQMKkPgYAAA=='}", + "vod_name": "推送视频播放", + "vod_pic": "https://github.catvod.com/https://raw.githubusercontent.com/hjdhnx/drpy-node/refs/heads/main/public/images/lives.jpg", + "vod_tag": "action" + }, + { + "name": "连续对话", + "action": "{'actionId':'连续对话','id':'talk','type':'input','title':'连续对话','tip':'请输入消息','value':'','msg':'开始新的对话','button':3,'imageUrl':'https://img2.baidu.com/it/u=1206278833,3265480730&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=800','imageHeight':200,'imageType':'card_pic_3','keep':true,'width':680,'height':800,'msgType':'long_text','httpTimeout':60,'canceledOnTouchOutside':false,'selectData':'新的对话:=清空AI对话记录'}" + }, + { + "name": "查看夸克cookie", + "action": "查看夸克cookie" + }, + { + "name": "设置夸克cookie", + "action": "设置夸克cookie" + }, + { + "name": "夸克扫码", + "action": "夸克扫码" + }, + { + "name": "设置玩偶域名", + "action": "{'actionId':'玩偶域名','id':'domain','type':'input','width':450,'title':'玩偶域名','tip':'请输入玩偶域名','value':'','msg':'选择或输入使用的域名','selectData':'1:=https://www.wogg.net/,2:=https://wogg.xxooo.cf/,3:=https://wogg.888484.xyz/,4:=https://www.wogg.bf/,5:=https://woggapi.333232.xyz/'}" + } + ] + }, + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_番茄小说[书]", + "name": "番茄小说[书](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/番茄小说[书]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "番茄小说[书]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_大象影视", + "name": "大象影视(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/大象影视?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "大象影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_腾云驾雾[官]", + "name": "腾云驾雾[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/腾云驾雾[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "腾云驾雾[官]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_央视大全[官]", + "name": "央视大全[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/央视大全[官]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "央视大全[官]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_PTT[优]", + "name": "PTT[优](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/PTT[优]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "PTT[优]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_光速[优]", + "name": "光速[优](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/光速[优]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "光速[优]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_木兮[优]", + "name": "木兮[优](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/木兮[优]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 1, + "title": "木兮[优]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_樱花动漫[优]", + "name": "樱花动漫[优](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/樱花动漫[优]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "樱花动漫[优]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_粤漫之家[优]", + "name": "粤漫之家[优](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/粤漫之家[优]?pwd=dzyyds", + "searchable": 0, + "filterable": 1, + "quickSearch": 0, + "title": "粤漫之家[优]", + "author": "不告诉你", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpy2_可可影视[优]", + "name": "可可影视[优](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "可可影视[优]", + "类型": "影视", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/可可影视[优].js?pwd=dzyyds" + }, + { + "key": "drpyS_hdmoli[盘]", + "name": "hdmoli[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/hdmoli[盘]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "hdmoli[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_二小[盘]", + "name": "二小[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/二小[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "二小[盘]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_傻样[盘]", + "name": "傻样[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/傻样[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "傻样[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_多多[盘]", + "name": "多多[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/多多[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "多多[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_木偶[盘]", + "name": "木偶[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/木偶[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "木偶[盘]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_欧哥[盘]", + "name": "欧哥[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/欧哥[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "欧哥[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_玩偶哥哥DM[盘]", + "name": "玩偶哥哥DM[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/玩偶哥哥DM[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "玩偶哥哥DM[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_玩偶哥哥[盘]", + "name": "玩偶哥哥[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/玩偶哥哥[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "玩偶哥哥[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_蜡笔ᵐ[盘]", + "name": "蜡笔ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5MWfh8zVTAI8Mua8gAAAA" + }, + { + "key": "drpyS_木偶ᵐ[盘]", + "name": "木偶ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5NmfF08ZtABDtdR4gAAAA" + }, + { + "key": "drpyS_玩偶ᵐ[盘]", + "name": "玩偶ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF53rfyaeM2FUMAXj8hgiIAAAA=" + }, + { + "key": "drpyS_虎斑ᵐ[盘]", + "name": "虎斑ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5MbPv2bSJAFZ8CcsgAAAA" + }, + { + "key": "drpyS_二小ᵐ[盘]", + "name": "二小ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5sqvn6YZ+AGwhNKsgAAAA" + }, + { + "key": "drpyS_多多ᵐ[盘]", + "name": "多多ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5umQWEAEA1+C5iSAAAAA=" + }, + { + "key": "drpyS_欧歌ᵐ[盘]", + "name": "欧歌ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF5tmb5s7U9AIVJTBogAAAA" + }, + { + "key": "drpyS_至臻ᵐ[盘]", + "name": "至臻ᵐ[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "网盘[模板]", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp//tMJvS9be5/vXacHElF50b75RftuAAWyZJQgAAAA" + }, + { + "key": "drpyS_至臻[盘]", + "name": "至臻[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/至臻[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "至臻[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_虎斑[盘]", + "name": "虎斑[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/虎斑[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "虎斑[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_蜡笔[盘]", + "name": "蜡笔[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/蜡笔[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "蜡笔[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_趣盘社[盘]", + "name": "趣盘社[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/趣盘社[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "趣盘社[盘]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_雷鲸小站[盘]", + "name": "雷鲸小站[盘](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/雷鲸小站[盘]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "雷鲸小站[盘]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_360影视[官]", + "name": "360影视[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/360影视[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "360影视[官]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_优酷[官]", + "name": "优酷[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/优酷[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "优酷[官]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_哔哩影视[官]", + "name": "哔哩影视[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/哔哩影视[官]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "哔哩影视[官]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_哔哩收藏[官]", + "name": "哔哩收藏[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/哔哩收藏[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "哔哩收藏[官]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp5ytPJK59N2fZier8eSAQA1H95AxkAAAA=" + }, + { + "key": "drpyS_奇珍异兽[官]", + "name": "奇珍异兽[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/奇珍异兽[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "奇珍异兽[官]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_哔哩教育[官]", + "name": "哔哩教育[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/我的哔哩[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "我的哔哩[官]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp5ytPJK59NnfmiaZMeSAQAgv7xPBkAAAA=" + }, + { + "key": "drpyS_哔哩少儿[官]", + "name": "哔哩少儿[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/我的哔哩[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "我的哔哩[官]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp5ytPJK59umPi0Zb8eSAQAOsH08xkAAAA=" + }, + { + "key": "drpyS_哔哩大全[官]", + "name": "哔哩大全[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/我的哔哩[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "我的哔哩[官]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp5ytPJK58uWf60dYUeSAQAZ2A+OBkAAAA=" + }, + { + "key": "drpyS_哔哩大杂烩[官]", + "name": "哔哩大杂烩[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/我的哔哩[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "我的哔哩[官]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tfzp5ytPJK58uWf5sbtPz5pV6IEEAolZcGBwAAAA=" + }, + { + "key": "drpyS_百忙无果[官]", + "name": "百忙无果[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/百忙无果[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "百忙无果[官]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_菜狗[官]", + "name": "菜狗[官](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/菜狗[官]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "菜狗[官]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpy2_百忙无果[官]", + "name": "百忙无果[官](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "百忙无果[官]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/百忙无果[官].js?pwd=dzyyds" + }, + { + "key": "drpy2_虎牙直播[官]", + "name": "虎牙直播[官](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "虎牙直播[官]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/虎牙直播[官].js?pwd=dzyyds" + }, + { + "key": "drpy2_豆瓣[官]", + "name": "豆瓣[官](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "豆瓣[官]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/豆瓣[官].js?pwd=dzyyds" + }, + { + "key": "hipy_py_2k动漫[AS]", + "name": "2k动漫[AS](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppSk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppSk", + "lang": "hipy", + "ext": { + "host": "https://dmsk.oss-rg-china-mainland.aliyuncs.com/dmapp/dmapi.txt", + "key": "ygcnbcobcegtgigg", + "iv": "4058263969143708" + } + }, + { + "key": "hipy_py_4K大全[AG³]", + "name": "4K大全[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "http://appcms.4kdq.icu", + "key": "R6FVRw4jsy4Hsitj" + } + }, + { + "key": "drpyS_16wMV[听]", + "name": "16wMV[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/16wMV[听]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "16wMV[听]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9rb+LR17ZMd7c/W9jybvUmvpKIEABrNdgcbAAAA" + }, + { + "key": "hipy_py_23影视[AM]", + "name": "23影视[AM](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppMuou?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppMuou", + "lang": "hipy", + "ext": { + "host": "https://muouapp.oss-cn-hangzhou.aliyuncs.com/MUOUAPP/764119293.txt", + "version": "4.2.0" + } + }, + { + "key": "drpyS_30wMV[听]", + "name": "30wMV[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/30wMV[听]?pwd=dzyyds", + "searchable": 0, + "filterable": 1, + "quickSearch": 0, + "title": "30wMV[听]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_啊哈DJ[听]", + "name": "啊哈DJ[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/啊哈DJ[听]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "啊哈DJ[听]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpy2_爱看机器人[虫]", + "name": "爱看机器人[虫](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "爱看机器人[虫]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/爱看机器人[虫].js?pwd=dzyyds" + }, + { + "key": "hipy_py_爱看剧Fax[ATV⁵]", + "name": "爱看剧Fax[ATV⁵](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppToV5?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppToV5", + "lang": "hipy", + "ext": "http://111.173.114.61:8762" + }, + { + "key": "hipy_py_白蛇[AG³]", + "name": "白蛇[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "http://tengxunyunaliyun.oss-cn-shanghai.aliyuncs.com/tengxunyun.txt", + "datakey": "n3l2tx5jdkp9s2c8" + } + }, + { + "key": "drpyS_百度盘[搜]", + "name": "百度盘[搜](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/百度盘[搜]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "百度盘[搜]", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_贝乐虎[儿]", + "name": "贝乐虎[儿](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/贝乐虎[儿]?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "贝乐虎[儿]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_哔哩哔哩", + "name": "哔哩哔哩(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/哔哩哔哩?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "哔哩哔哩", + "lang": "hipy", + "ext": "" + }, + { + "key": "drpyS_播客[听]", + "name": "播客[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/播客[听]?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "播客[听]", + "类型": "听书", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_博看听书[听]", + "name": "博看听书[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/博看听书[听]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "博看听书[听]", + "类型": "听书", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_采王2024", + "name": "采王2024(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/采集之王[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "采集之王[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9ne/nJ2m5GBkcnLuTOfNTTqgURVDFUAlKCu7yAAAAA=" + }, + { + "key": "drpyS_采王2025", + "name": "采王2025(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/采集之王[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "采集之王[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9ne/nJ2m5GBkenLuTOfNTTqgURVDFUAfHtVViAAAAA=" + }, + { + "key": "drpyS_采王成人[密]", + "name": "采王成人[密](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/采集之王[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "采集之王[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9ne/nJ2W/TT9W2xL+fOfNbQqAcSVjFUAQAd5YgpIQAAAA==" + }, + { + "key": "drpyS_采王道长[合]", + "name": "采王道长[合](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/采集之王[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "采集之王[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9ne/nJ228u5M581NOqBRFQMAcGPDNobAAAA" + }, + { + "key": "drpyS_采王zy[密]", + "name": "采王zy[密](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/采集之王[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "采集之王[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9ne/nJ2W3RVZezLuTOfNTTqgURVDAGu/MCKHwAAAA==" + }, + { + "key": "hipy_py_仓鼠[AG²]", + "name": "仓鼠[AG²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://newappcms.cs4k.top", + "key": "Z98KXaLtO2wC1Pte", + "path": "/api.php/qijiappapi" + } + }, + { + "key": "drpyS_草榴社区[密]", + "name": "草榴社区[密](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/草榴社区[密]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "草榴社区[密]", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_畅看[ATV⁵]", + "name": "畅看[ATV⁵](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppToV5?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppToV5", + "lang": "hipy", + "ext": "http://118.89.203.120:8366" + }, + { + "key": "hipy_py_大米[AV²]", + "name": "大米[AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "https://dmz8k4.wiki" + }, + { + "key": "drpyS_点歌欢唱[B]", + "name": "点歌欢唱[B](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/点歌欢唱[B]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "点歌欢唱[B]", + "logo": "https://tva1.sinaimg.cn/crop.6.7.378.378.1024/a22d2331jw8f6hs4xrb4kj20ay0ayacy.jpg", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tf9rb+LR17ZMd7c/W9jybvUmvpKIEABrNdgcbAAAA" + }, + { + "key": "drpyS_顶点小说[书]", + "name": "顶点小说[书](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/顶点小说[书]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "顶点小说[书]", + "类型": "小说", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_动作代理测试", + "name": "动作代理测试(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/动作代理测试?do=py&pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "动作代理测试", + "lang": "hipy", + "ext": "" + }, + { + "key": "drpyS_动作交互", + "name": "动作交互(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/动作交互?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "动作交互", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_番茄短剧", + "name": "番茄短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/番茄短剧?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "番茄短剧", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_番喜[ATV⁵]", + "name": "番喜[ATV⁵](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppToV5?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppToV5", + "lang": "hipy", + "ext": "http://118.89.203.120:8762" + }, + { + "key": "drpyS_非凡采集[采]", + "name": "非凡采集[采](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/非凡采集[采]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 1, + "title": "非凡采集[采]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "catvod_风车动漫", + "name": "风车动漫(cat)", + "type": 4, + "api": "http://127.0.0.1:5757/api/风车动漫?do=cat&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "风车动漫", + "lang": "cat", + "ext": "" + }, + { + "key": "drpyS_凤凰FM[听]", + "name": "凤凰FM[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/凤凰FM[听]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 1, + "title": "凤凰FM[听]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_瓜萌[AG³]", + "name": "瓜萌[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "https://www.guahd.com/1.txt", + "key": "f2A7D4B9E8C16531" + } + }, + { + "key": "drpyS_光映视界_AppShark", + "name": "光映视界[M](SHARK)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppShark[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "ext": "../json/App模板配置.json$光映视界" + }, + { + "key": "drpyS_鬼片之家", + "name": "鬼片之家(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/鬼片之家?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "鬼片之家", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_海狗视频[Hs]", + "name": "海狗视频[Hs](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppHs?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppHs", + "lang": "hipy", + "ext": { + "host": "https://dy.stxbed.com", + "app_id": "haigou", + "deviceid": "", + "versionCode": "20100", + "UMENG_CHANNEL": "zhuan" + } + }, + { + "key": "drpyS_好乐影视", + "name": "好乐影视(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/好乐影视?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "好乐影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_好震惊[AV²]", + "name": "好震惊[AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "http://v.lnhaozhenjin.cn" + }, + { + "key": "hipy_py_河狸![AV²]", + "name": "河狸![AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "https://www.heli888.cc" + }, + { + "key": "catvod_河南电视代理", + "name": "河南电视代理(cat)", + "type": 4, + "api": "http://127.0.0.1:5757/api/河南电视代理?do=cat&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "河南电视代理", + "lang": "cat", + "ext": "" + }, + { + "key": "hipy_py_火猫[AG¹]", + "name": "火猫[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "http://154.12.90.59:14500", + "key": "J6AIORKJ3PQOJKM3" + } + }, + { + "key": "hipy_py_火猫影视[AFX]", + "name": "火猫影视[AFX](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppFox?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppFox", + "lang": "hipy", + "ext": { + "host": "https://cunchu8.obs.cn-north-4.myhuaweicloud.com/config.json", + "parse": { + "JL4K": "http://194.147.100.155:7891/?url=" + } + } + }, + { + "key": "hipy_py_即看影视[AS]", + "name": "即看影视[AS](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppSk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppSk", + "lang": "hipy", + "ext": { + "host": "https://skyappdata-1321528676.cos.accelerate.myqcloud.com/4kapp/appipr.txt", + "key": "ygcnbckhcuvygdyb", + "iv": "4023892775143708" + } + }, + { + "key": "drpyS_集百动漫", + "name": "集百动漫(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/集百动漫?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 0, + "title": "集百动漫", + "lang": "ds", + "ext": "" + }, + { + "key": "catvod_荐片", + "name": "荐片(cat)", + "type": 4, + "api": "http://127.0.0.1:5757/api/荐片?do=cat&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "荐片", + "lang": "cat", + "ext": "" + }, + { + "key": "drpyS_荐片", + "name": "荐片(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/荐片?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "荐片", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_锦鲤短剧", + "name": "锦鲤短剧(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/锦鲤短剧?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "锦鲤短剧", + "lang": "hipy", + "ext": "" + }, + { + "key": "hipy_py_鲸鱼影视[AG³]", + "name": "鲸鱼影视[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "https://jingyu4k-1312635929.cos.ap-nanjing.myqcloud.com/1.json", + "datakey": "AAdgrdghjfgswerA", + "api": 2 + } + }, + { + "key": "hipy_py_橘子[AG¹]", + "name": "橘子[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://api1.bffree.cn", + "key": "2015692015692015" + } + }, + { + "key": "hipy_py_橘子TV[Ayq]", + "name": "橘子TV[Ayq](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppYqk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppYqk", + "lang": "hipy", + "ext": { + "host": "https://gapi0725.5p8jcjc.com/config.json,https://gapi0725.olrv5gz.com/config.json,https://gapi0725.mvljeat.com/config.json,https://jzapp-1318635097.cos.ap-shanghai.myqcloud.com/config.json,https://juzi-config-1360051343.cos.ap-shanghai.myqcloud.com/config.json", + "appId": "fea23e11fc1241409682880e15fb2851", + "appkey": "f384b87cc9ef41e4842dda977bae2c7f", + "udid": "bfc18c00-c866-46cb-8d7b-121c39b942d4", + "bundlerId": "com.voraguzzee.ts", + "source": "1003_default", + "version": "1.0.1", + "versionCode": 1000 + } + }, + { + "key": "hipy_py_剧下饭[AV²]", + "name": "剧下饭[AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "http://jxfmax.juxiafan.com" + }, + { + "key": "hipy_py_剧下饭[AV⁶]", + "name": "剧下饭[AV⁶](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV6?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV6", + "lang": "hipy", + "ext": "http://jxfmax.juxiafan.com/icciu_api.php/v1.vod" + }, + { + "key": "hipy_py_剧永久[AV⁶]", + "name": "剧永久[AV⁶](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV6?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV6", + "lang": "hipy", + "ext": { + "api": "http://yjyi.juyongjiu.com/icciu_api.php/v1.vod", + "datasignkey": "6QQNUsP3PkD2ajJCPCY8", + "apisignkey": "lvdoutv-1.0.0" + } + }, + { + "key": "drpyS_开眼", + "name": "开眼(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/开眼?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "开眼", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_看客联盟[AS]", + "name": "看客联盟[AS](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppSk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppSk", + "lang": "hipy", + "ext": { + "host": "https://kankelm.cn:2024/appdomain.txt", + "key": "ygcnbcvybqqckwqy", + "iv": "1583560747143708" + } + }, + { + "key": "drpyS_可可影视", + "name": "可可影视(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/可可影视?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "可可影视", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_酷我听书[听]", + "name": "酷我听书[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/酷我听书[听]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "酷我听书[听]", + "类型": "听书", + "lang": "ds", + "ext": "" + }, + { + "key": "catvod_蓝莓聚合短剧[B]", + "name": "蓝莓聚合短剧[B](cat)", + "type": 3, + "api": "http://127.0.0.1:5757/cat/蓝莓聚合短剧[B].js?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "蓝莓聚合短剧[B]", + "lang": "cat", + "author": "phototake", + "logo": "https://img2.baidu.com/it/u=1444140254,1996298586&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", + "comment": "感谢蓝莓大佬友情支持!", + "ext": "" + }, + { + "key": "drpyS_蓝鹰_Appget", + "name": "蓝鹰[M](GET)", + "type": 4, + "api": "http://127.0.0.1:5757/api/Appget[模板]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "ext": "../json/App模板配置.json$蓝鹰" + }, + { + "key": "hipy_py_懒懒视频", + "name": "懒懒视频(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/懒懒视频?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "懒懒视频", + "lang": "hipy", + "ext": "" + }, + { + "key": "hipy_py_玲珑[AG¹]", + "name": "玲珑[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://mac.555618.xyz", + "key": "#getapp@TMD@2025" + } + }, + { + "key": "drpyS_六月听书[听]", + "name": "六月听书[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/六月听书[听]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "六月听书[听]", + "logo": "https://pic.xlhs.com/up/2021-11/2021112985176843.png", + "类型": "听书", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_萝卜[AG³]", + "name": "萝卜[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "https://apiapplbys.lbys.app:5678", + "key": "apiapplbyskey168" + } + }, + { + "key": "catvod_猫测试", + "name": "猫测试(cat)", + "type": 4, + "api": "http://127.0.0.1:5757/api/猫测试?do=cat&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "猫测试", + "lang": "cat", + "ext": "" + }, + { + "key": "drpyS_美颜怪[擦]", + "name": "美颜怪[擦](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/美颜怪[擦]?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "美颜怪[擦]", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_米诺-旧[AG¹]", + "name": "米诺-旧[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "http://www.milkidc.cn", + "key": "20c79c979da8db0f" + } + }, + { + "key": "hipy_py_米兔-旧[AG¹]", + "name": "米兔-旧[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "http://new.tkbot.fun", + "key": "d032c12876bc6848" + } + }, + { + "key": "drpyS_牛牛短剧", + "name": "牛牛短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/牛牛短剧?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "牛牛短剧", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_皮皮虾[ATV⁵]", + "name": "皮皮虾[ATV⁵](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppToV5?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppToV5", + "lang": "hipy", + "ext": "http://38.55.237.41:8762" + }, + { + "key": "drpyS_七猫短剧", + "name": "七猫短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/七猫短剧?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "七猫短剧", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_七猫小说[书]", + "name": "七猫小说[书](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/七猫小说[书]?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "七猫小说[书]", + "logo": "https://cdn-front.qimao.com/global/static/images/favicon2022.ico", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_七猫小说[书]", + "name": "七猫小说[书](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/七猫小说[书]?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "七猫小说[书]", + "logo": "https://cdn-front.qimao.com/global/static/images/favicon2022.ico", + "lang": "hipy", + "ext": "" + }, + { + "key": "hipy_py_七月[AG³]", + "name": "七月[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "https://99.jl8.top/1.txt", + "key": "xnybssspqtwotuwj" + } + }, + { + "key": "drpyS_清风DJ[听]", + "name": "清风DJ[听](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/清风DJ[听]?pwd=dzyyds", + "searchable": 0, + "filterable": 1, + "quickSearch": 0, + "title": "清风DJ[听]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_热剧[AV²]", + "name": "热剧[AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "https://www.rebovod.com" + }, + { + "key": "drpyS_软鸭短剧", + "name": "软鸭短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/软鸭短剧?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "软鸭短剧", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_森林动漫[AF]", + "name": "森林动漫[AF](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppFei?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppFei", + "lang": "hipy", + "ext": "https://ysa.yy-fun.cc/feiapp" + }, + { + "key": "hipy_py_闪影[AV²]", + "name": "闪影[AV²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2²?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2²", + "lang": "hipy", + "ext": "http://38.47.213.61:41271" + }, + { + "key": "hipy_py_拾光视频[Hs]", + "name": "拾光视频[Hs](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppHs?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppHs", + "lang": "hipy", + "ext": { + "host": "https://dy.jmzp.net.cn", + "app_id": "shiguang", + "deviceid": "", + "versionCode": "10000", + "UMENG_CHANNEL": "guan" + } + }, + { + "key": "hipy_py_天空影视", + "name": "天空影视(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/天空影视?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "天空影视", + "lang": "hipy", + "ext": "" + }, + { + "key": "drpyS_兔小贝[儿]", + "name": "兔小贝[儿](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/兔小贝[儿]?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "兔小贝[儿]", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_王子TV", + "name": "王子TV(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/王子TV?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "王子TV", + "logo": "https://i-blog.csdnimg.cn/blog_migrate/2621e710a94ab40ba66645d47f296aaf.gif", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_网盘资源[搜]", + "name": "网盘资源[搜](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/网盘资源[搜]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "网盘资源[搜]", + "类型": "搜索", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_未来影视[AFX]", + "name": "未来影视[AFX](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppFox?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppFox", + "lang": "hipy", + "ext": { + "host": "http://kumiao.yzbao.com.cn", + "parse": { + "qq|qiyi|mgtv|youku|bilibili": "https://api.qljson.xyz/api/?key=67f6a108dc6d84eaf81ac58417c1f72a&url=" + } + } + }, + { + "key": "hipy_py_五八[AG¹]", + "name": "五八[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://dy.58ys.vip", + "key": "JEWibY1AgWF0V1xx" + } + }, + { + "key": "drpyS_西饭短剧", + "name": "西饭短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/西饭短剧?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "西饭短剧", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "drpy2_相声随身听[听]", + "name": "相声随身听[听](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "相声随身听[听]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/相声随身听[听].js?pwd=dzyyds" + }, + { + "key": "drpyS_小绿书[B]", + "name": "小绿书[B](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/小绿书[B]?pwd=dzyyds", + "searchable": 0, + "filterable": 1, + "quickSearch": 0, + "title": "小绿书[B]", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_欣欣影视", + "name": "欣欣影视(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/欣欣影视?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "欣欣影视", + "lang": "hipy", + "ext": "" + }, + { + "key": "hipy_py_新浪资源", + "name": "新浪资源(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/新浪资源?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "新浪资源", + "lang": "hipy", + "ext": "" + }, + { + "key": "drpyS_星芽短剧", + "name": "星芽短剧(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/星芽短剧?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 1, + "title": "星芽短剧", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_兄弟盘[搜]", + "name": "兄弟盘[搜](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/兄弟盘[搜]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 1, + "title": "兄弟盘[搜]", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_雄鹰[AG²]", + "name": "雄鹰[AG²](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://lanyinghz.oss-cn-hangzhou.aliyuncs.com/lanyingxmy.txt", + "key": "ca94b06ca359d80e", + "path": "/api.php/qijiappapi" + } + }, + { + "key": "hipy_py_雪豹视频[Hs]", + "name": "雪豹视频[Hs](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppHs?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppHs", + "lang": "hipy", + "ext": { + "host": "https://dy.jszdzs.com", + "app_id": "xuebao", + "deviceid": "", + "versionCode": "21300", + "UMENG_CHANNEL": "share" + } + }, + { + "key": "drpy2_丫丫电子书[书]", + "name": "丫丫电子书[书](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "丫丫电子书[书]", + "类型": "小说", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/丫丫电子书[书].js?pwd=dzyyds" + }, + { + "key": "hipy_py_丫丫动漫[AG¹]", + "name": "丫丫动漫[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "http://tv.yy-fun.cc", + "key": "qkxnwkfjwpcnwycl" + } + }, + { + "key": "hipy_py_呀哩4K[AF]", + "name": "呀哩4K[AF](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppFei?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppFei", + "lang": "hipy", + "ext": "https://ysc.yy-fun.cc/feiapp" + }, + { + "key": "hipy_py_一起看[Ayq]", + "name": "一起看[Ayq](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppYqk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppYqk", + "lang": "hipy", + "ext": { + "host": "https://gapi0320.3njzmrx1.com/config.json,https://gapi0320.lq0okex8.com/config.json,https://gapi0320.zabqs8xp.com/config.json,https://yappconfig-20250628-1318635097.cos.ap-shanghai.myqcloud.com/config.json,https://yconfig-20250628-1360051343.cos.ap-guangzhou.myqcloud.com/config.json", + "appId": "d6d520ea90904f1ba680ed6c9c9f9007", + "appkey": "70af67d2b6cf47679b397ea4c1886877", + "udid": "bfc18c00-c866-46cb-8d7b-121c39b942d4", + "bundlerId": "com.flotimingo.ts", + "source": "1001_default", + "version": "1.3.10", + "versionCode": 1104 + } + }, + { + "key": "hipy_py_依赖测试", + "name": "依赖测试(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/依赖测试?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "依赖测试", + "lang": "hipy", + "ext": "" + }, + { + "key": "drpyS_樱漫[漫]", + "name": "樱漫[漫](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/樱漫[漫]?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 0, + "title": "樱漫[漫]", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_云云[AG³]", + "name": "云云[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "https://staraugust123456.oss-cn-hangzhou.aliyuncs.com/1.txt", + "datakey": "staraugust123456", + "api": 2 + } + }, + { + "key": "hipy_py_掌上追剧[AG³]", + "name": "掌上追剧[AG³](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/getapp3.4.4?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "getapp3.4.4", + "lang": "hipy", + "ext": { + "host": "http://tvb.yy-fun.cc", + "key": "jcTz6Jda2aKrH8Tk" + } + }, + { + "key": "drpyS_直播转点播[合]", + "name": "直播转点播[合](DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/直播转点播[合]?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "直播转点播[合]", + "lang": "ds", + "ext": "H4sIAAAAAAAAA9PT088qzs/Tz8ksSzVKzi3WA/EAuuBHLxUAAAA=" + }, + { + "key": "drpy2_种子音乐[听]", + "name": "种子音乐[听](DR2)", + "type": 3, + "api": "http://127.0.0.1:5757/public/drpy/drpy2.min.js", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "种子音乐[听]", + "lang": "dr2", + "ext": "http://127.0.0.1:5757/js/种子音乐[听].js?pwd=dzyyds" + }, + { + "key": "hipy_py_紫云[AV¹]", + "name": "紫云[AV¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV1?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV1", + "lang": "hipy", + "ext": "http://ziyuncms.feifan12.xyz/api.php" + }, + { + "key": "drpyS_APP模板配置", + "name": "APP模板配置(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/APP模板配置?pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 0, + "title": "APP模板配置", + "more": { + "sourceTag": "设置,动作", + "actions": [ + { + "name": "APP模板|配置相关", + "action": "{\"actionId\":\"单选菜单\",\"type\":\"menu\",\"title\":\"Action菜单\",\"width\":500,\"column\":1,\"option\":[\"导入分享配置$menu1\",\"分享APP配置$menu2\"],\"selectedIndex\":0}" + } + ] + }, + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_AppV2¹", + "name": "AppV2¹(hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppV2¹?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppV2¹", + "lang": "hipy", + "ext": "" + }, + { + "key": "hipy_py_Free影视[AS]", + "name": "Free影视[AS](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppSk?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppSk", + "lang": "hipy", + "ext": { + "host": "https://sk.xiaoyaoys.top/skkkkkkk.txt", + "key": "ygcnbcczduwydmrs", + "iv": "4672921073143708" + } + }, + { + "key": "drpyS_gaze", + "name": "gaze(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/gaze?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "gaze", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_ikanbot", + "name": "ikanbot(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/ikanbot?pwd=dzyyds", + "searchable": 2, + "filterable": 0, + "quickSearch": 0, + "title": "ikanbot", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_iptv", + "name": "iptv(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/iptv?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "iptv", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_OMOfun[AG¹]", + "name": "OMOfun[AG¹](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppGet?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppGet", + "lang": "hipy", + "ext": { + "host": "https://app.omofun1.top", + "key": "66dc309cbeeca454" + } + }, + { + "key": "drpyS_php测试", + "name": "php测试(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/php?pwd=dzyyds", + "searchable": 1, + "filterable": 0, + "quickSearch": 0, + "title": "php", + "类型": "影视", + "lang": "ds", + "ext": "H4sIAAAAAAAAA6tWysgvLlGyUsooKSkottLXLy8v10tKzEwp1UvOz1WqBQCTzRcDIAAAAA==" + }, + { + "key": "push_agent", + "name": "push_agent(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/push_agent?pwd=dzyyds", + "searchable": 0, + "filterable": 0, + "quickSearch": 0, + "title": "push_agent", + "lang": "ds", + "ext": "" + }, + { + "key": "drpyS_TVB云播", + "name": "TVB云播(DS)", + "type": 4, + "api": "http://127.0.0.1:5757/api/TVB云播?pwd=dzyyds", + "searchable": 2, + "filterable": 1, + "quickSearch": 0, + "title": "TVB云播", + "类型": "影视", + "lang": "ds", + "ext": "" + }, + { + "key": "hipy_py_TVB云播[AFX]", + "name": "TVB云播[AFX](hipy)", + "type": 4, + "api": "http://127.0.0.1:5757/api/AppFox?do=py&pwd=dzyyds", + "searchable": 1, + "filterable": 1, + "quickSearch": 1, + "title": "AppFox", + "lang": "hipy", + "ext": "http://app.hktvyb.cc" + } + ], + "parses": [ + { + "name": "JSON并发", + "url": "http://127.0.0.1:5757/parse/JSON并发?url=", + "type": 2, + "ext": { + "flag": [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "JSON合集", + "url": "http://127.0.0.1:5757/parse/JSON合集?url=", + "type": 3, + "ext": { + "flag": [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "虾米", + "url": "http://127.0.0.1:5757/parse/虾米?url=", + "type": 1, + "ext": { + "flag": [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36", + "Referer": "https://jx.xmflv.com" + } + }, + { + "name": "奇奇", + "url": "http://127.0.0.1:5757/parse/奇奇?url=", + "type": 1, + "ext": { + "flag": [ + "qiyi", + "爱奇艺", + "奇艺" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "芒果关姐", + "url": "http://127.0.0.1:5757/parse/芒果关姐?url=", + "type": 1, + "ext": { + "flag": [ + "imgo", + "mgtv", + "芒果" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "json1", + "url": "http://127.0.0.1:5757/parse/json1?url=", + "type": 1, + "ext": { + "flag": [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36" + } + }, + { + "name": "web1", + "url": "https://bfq.cfwlgzs.cn/player?url=", + "type": 0, + "ext": { + "flag": [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷" + ] + }, + "header": { + "User-Agent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36" + } + }, + { + "name": "J1", + "url": "https://zy.qiaoji8.com/gouzi.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "J2", + "url": "https://jxjson.icu/neibu.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W1", + "url": "https://jx.xymp4.cc/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W11", + "url": "https://www.ckplayer.vip/jiexi/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "295关姐", + "url": "http://127.0.0.1:5759/295yun.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "腾讯关姐", + "url": "http://127.0.0.1:5759/tencent.php/?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "HGvip", + "url": "http://1.94.221.189:88/algorithm.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + } + ], + "lives": [ + { + "name": "直播", + "type": 0, + "url": "http://127.0.0.1:5757/public/lives/tv.m3u", + "playerType": 1, + "ua": "okhttp/3.12.13", + "epg": "https://iptv.crestekk.cn/epgphp/index.php?ch={name}&date={date}", + "logo": "https://live.mxdyeah.top/logo/{name}.png" + } + ] +} \ No newline at end of file diff --git a/dashboard/src/mock/middlewares.js b/dashboard/src/mock/middlewares.js new file mode 100644 index 0000000..bd11bde --- /dev/null +++ b/dashboard/src/mock/middlewares.js @@ -0,0 +1,12 @@ +module.exports = (request, response, next) => { + if (request.method === 'POST') { + request.method = 'GET' + request.query = request.body + } + + // 处理IE8下的文件上传 + if ((request.headers['content-type'] || '').startsWith('multipart/form-data')) { + response.header('content-type', 'text/html') + } + next() +} diff --git a/dashboard/src/mock/multiInputX.json b/dashboard/src/mock/multiInputX.json new file mode 100644 index 0000000..2e96d33 --- /dev/null +++ b/dashboard/src/mock/multiInputX.json @@ -0,0 +1,5 @@ +{ + "vod_id": "{\"actionId\":\"多项输入\",\"type\":\"multiInputX\",\"canceledOnTouchOutside\":true,\"title\":\"Action多项输入(multiInputX)\",\"width\":716,\"height\":-300,\"bottom\":1,\"dimAmount\":0.3,\"msg\":\"“平行志愿”是普通高校招生平行志愿投档模式的简称,平行志愿投档又可分为按“院校+专业组”(以下简称院校专业组)平行志愿投档和按专业平行志愿投档两类。\",\"button\":3,\"input\":[{\"id\":\"item1\",\"name\":\"文件夹路径(文件夹选择)\",\"tip\":\"请输入文件夹路径\",\"value\":\"\",\"selectData\":\"[folder]\",\"validation\":\"\",\"inputType\":0,\"help\":\"“平行志愿”是普通高校招生平行志愿投档模式的简称,平行志愿投档又可分为按“院校+专业组”(以下简称院校专业组)平行志愿投档和按专业平行志愿投档两类。\"},{\"id\":\"item2\",\"name\":\"日期(日期选择)\",\"tip\":\"请输入项目2内容\",\"value\":\"\",\"selectData\":\"[calendar]\",\"validation\":\"\",\"inputType\":0},{\"id\":\"item3\",\"name\":\"文件路径(文件选择)\",\"tip\":\"请输入文件路径\",\"value\":\"\",\"selectData\":\"[file]\",\"inputType\":0},{\"id\":\"item4\",\"name\":\"多项选择\",\"tip\":\"请输入多项内容,以“,”分隔\",\"value\":\"\",\"selectData\":\"[请选择字母]a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z\",\"selectWidth\":640,\"multiSelect\":true,\"selectColumn\":4,\"inputType\":0},{\"id\":\"item5\",\"name\":\"项目5\",\"tip\":\"请输入项目5内容\",\"value\":\"\",\"multiLine\":5},{\"id\":\"item6\",\"name\":\"项目6\",\"tip\":\"请输入项目6内容,密码,inputType: 129\",\"value\":\"\",\"inputType\":129},{\"id\":\"item7\",\"name\":\"图像base64(图像文件选择)\",\"tip\":\"请输入项目7内容\",\"value\":\"\",\"selectData\":\"[image]\",\"multiLine\":3,\"inputType\":0},{\"id\":\"item8\",\"name\":\"单项选择\",\"tip\":\"请输入项目8内容\",\"value\":\"李四\",\"selectData\":\"[请选择]张三,李四,王五,赵六\",\"onlyQuickSelect\":true},{\"id\":\"item9\",\"name\":\"项目9\\n“平行志愿”是普通高校招生平行志愿投档模式的简称,平行志愿投档又可分为按“院校+专业组”(以下简称院校专业组)平行志愿投档和按专业平行志愿投档两类。\",\"tip\":\"请输入项目9内容\",\"value\":\"可歌可泣\",\"selectData\":\"[请选择]可爱,可惜,可人,可以,可歌可泣,可恶\",\"validation\":\"\",\"quickSelect\":true,\"inputType\":0,\"help\":\"

“平行志愿”

是普通高校 招生平行 志愿投档模式的简称,平行志愿投档又可分为按“院校+专业组”(以下简称院校专业组)平行志愿投档和按专业平行志愿         投档两类。\"},{\"id\":\"item10\",\"name\":\"项目10\",\"tip\":\"请输入项目10内容\",\"value\":\"\"},{\"id\":\"item11\",\"name\":\"项目11\",\"tip\":\"请输入项目11内容\",\"value\":\"\"}]}", + "vod_name": "多项输入", + "vod_tag": "action" +} \ No newline at end of file diff --git a/dashboard/src/router/index.js b/dashboard/src/router/index.js index 3f7a873..b0f6c60 100644 --- a/dashboard/src/router/index.js +++ b/dashboard/src/router/index.js @@ -1,23 +1,72 @@ import {createRouter, createWebHistory} from 'vue-router'; import Home from '@/views/Home.vue'; import Video from '@/views/Video.vue'; +import VideoDetail from '@/views/VideoDetail.vue'; import Live from '@/views/Live.vue'; -import Settings from '@/views/Settings.vue'; +import Parser from '@/views/Parser.vue'; import Collection from '@/views/Collection.vue'; import History from '@/views/History.vue'; +import Settings from '@/views/Settings.vue'; +import BookGallery from '@/views/BookGallery.vue'; +import ActionTest from '@/views/ActionTest.vue'; +import ActionDebugTest from '@/views/ActionDebugTest.vue'; +import VideoTest from '@/views/VideoTest.vue'; +import CSPTest from '@/views/CSPTest.vue'; +import SearchAggregation from '@/views/SearchAggregation.vue'; + const routes = [ - {path: '/', component: Home}, - {path: '/video', component: Video}, - {path: '/live', component: Live}, - {path: '/settings', component: Settings}, - {path: '/collection', component: Collection}, - {path: '/history', component: History}, + {path: '/', component: Home, name: 'Home'}, + {path: '/video', component: Video, name: 'Video'}, + {path: '/video/:id', component: VideoDetail, name: 'VideoDetail', props: true}, + {path: '/live', component: Live, name: 'Live'}, + {path: '/settings', component: Settings, name: 'Settings'}, + {path: '/collection', component: Collection, name: 'Collection'}, + {path: '/book-gallery', component: BookGallery, name: 'BookGallery'}, + {path: '/local-book-reader/:bookId', component: () => import('@/views/LocalBookReader.vue'), name: 'LocalBookReader', props: true}, + {path: '/download-manager', component: () => import('@/components/downloader/NovelDownloader.vue'), name: 'DownloadManager'}, + {path: '/history', component: History, name: 'History'}, + {path: '/parser', component: Parser, name: 'Parser'}, + {path: '/action-test', component: ActionTest, name: 'ActionTest'}, + {path: '/action-debug-test', component: ActionDebugTest, name: 'ActionDebugTest'}, + {path: '/video-test', component: VideoTest, name: 'VideoTest'}, + {path: '/csp-test', component: CSPTest, name: 'CSPTest'}, + {path: '/search', component: SearchAggregation, name: 'SearchAggregation'}, + + // 404 fallback路由 - 必须放在最后 + {path: '/:pathMatch(.*)*', redirect: '/'} ]; +// 获取base路径,支持子目录部署 +const getBasePath = () => { + // 在生产环境中,如果设置了VITE_BASE_PATH环境变量,使用它 + if (import.meta.env.PROD && import.meta.env.VITE_BASE_PATH) { + return import.meta.env.VITE_BASE_PATH; + } + // 否则使用Vite的BASE_URL + return import.meta.env.BASE_URL; +}; + const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), + history: createWebHistory(getBasePath()), routes, + // 滚动行为 + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition; + } else { + return { top: 0 }; + } + } +}); + +// 路由守卫 - 可以在这里添加权限检查等逻辑 +router.beforeEach((to, from, next) => { + // 设置页面标题 + if (to.name) { + document.title = `DrPlayer - ${to.name}`; + } + next(); }); export default router; diff --git a/dashboard/src/services/backupService.js b/dashboard/src/services/backupService.js new file mode 100644 index 0000000..ab2d7f5 --- /dev/null +++ b/dashboard/src/services/backupService.js @@ -0,0 +1,546 @@ +/** + * 数据备份还原服务 + * 负责应用数据的备份和还原功能 + */ + +import { Message } from '@arco-design/web-vue' +import siteService from '@/api/services/site' + +// 备份数据版本号,用于兼容性检查 +const BACKUP_VERSION = '1.0.0' + +// 需要备份的localStorage键名 +const BACKUP_KEYS = { + // 设置相关 + ADDRESS_SETTINGS: 'addressSettings', + APP_SETTINGS: 'appSettings', + CSP_CONFIG: 'csp_config', + + // 用户数据 + FAVORITES: 'drplayer-favorites', + WATCH_HISTORY: 'drplayer_watch_history', + DAILY_STATS: 'drplayer_daily_stats', + WEEKLY_STATS: 'drplayer_weekly_stats', + HISTORIES: 'drplayer_histories', // 历史页面数据 + + // 站点数据 + SITE_STORE: 'siteStore', + CONFIG_URL: 'drplayer_config_url', + LIVE_CONFIG_URL: 'drplayer_live_config_url', + CURRENT_SITE: 'drplayer_current_site', + + // 聚合搜索相关 + SEARCH_AGGREGATION_SETTINGS: 'searchAggregationSettings', // 聚合搜索源选择设置 + SEARCH_AGGREGATION_PAGE_STATE: 'pageState_searchAggregation', // 聚合搜索页面状态 + SEARCH_HISTORY: 'drplayer_search_history', // 搜索历史记录 + + // 其他功能设置 + SKIP_SETTINGS: 'skipSettings', + PARSER_CONFIG: 'parserConfig', + PARSERS: 'drplayer_parsers', // 解析器数据 + SIDEBAR_COLLAPSED: 'sidebarCollapsed', + PAGE_STATE: 'pageState', + + // 开发者调试设置 + DEBUG_SETTINGS: 'debugSettings', + + // 悬浮组件相关 + FLOATING_BUTTON_POSITION: 'floating-iframe-button-position', + FLOATING_WINDOW_POSITION: 'floating-iframe-window-position', + FLOATING_WINDOW_SIZE: 'floating-iframe-window-size' +} + +// 存储为字符串的键(不需要JSON解析) +const STRING_KEYS = new Set([ + BACKUP_KEYS.CONFIG_URL, + BACKUP_KEYS.LIVE_CONFIG_URL, + BACKUP_KEYS.CURRENT_SITE, + BACKUP_KEYS.SIDEBAR_COLLAPSED +]) + +/** + * 获取当前时间戳字符串 + */ +const getTimestamp = () => { + return new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) +} + +/** + * 从localStorage获取数据 + */ +const getLocalStorageData = (key, defaultValue = null) => { + try { + const data = localStorage.getItem(key) + if (!data) return defaultValue + + // 如果是字符串类型的键,直接返回字符串 + if (STRING_KEYS.has(key)) { + return data + } + + // 其他键尝试JSON解析 + return JSON.parse(data) + } catch (error) { + console.warn(`获取 ${key} 数据失败:`, error) + return defaultValue + } +} + +/** + * 设置localStorage数据 + */ +const setLocalStorageData = (key, data) => { + try { + if (data === null || data === undefined) { + localStorage.removeItem(key) + } else { + // 如果是字符串类型的键,直接存储字符串 + if (STRING_KEYS.has(key)) { + localStorage.setItem(key, data || '') + } else { + // 其他键使用JSON序列化 + localStorage.setItem(key, JSON.stringify(data)) + } + } + return true + } catch (error) { + console.error(`保存 ${key} 数据失败:`, error) + return false + } +} + +/** + * 收集所有地址配置历史数据 + */ +const collectAddressHistories = () => { + const addressHistories = {} + const addressHistoryKeys = [ + 'address-history-vod-config', + 'address-history-live-config', + 'address-history-proxy-access', + 'address-history-proxy-play', + 'address-history-proxy-sniff' + ] + + addressHistoryKeys.forEach(key => { + const data = getLocalStorageData(key, []) + if (data && data.length > 0) { + addressHistories[key] = data + } + }) + + return addressHistories +} + +/** + * 收集所有需要备份的数据 + */ +export const collectBackupData = () => { + const backupData = { + // 备份元信息 + meta: { + version: BACKUP_VERSION, + timestamp: new Date().toISOString(), + appName: 'DrPlayer', + description: '应用数据备份文件' + }, + + // 设置数据 + settings: { + // 地址设置 + addressSettings: getLocalStorageData(BACKUP_KEYS.ADDRESS_SETTINGS, {}), + // 应用设置 + appSettings: getLocalStorageData(BACKUP_KEYS.APP_SETTINGS, {}), + // CSP配置 + cspConfig: getLocalStorageData(BACKUP_KEYS.CSP_CONFIG, {}), + // 跳过设置 + skipSettings: getLocalStorageData(BACKUP_KEYS.SKIP_SETTINGS, {}), + // 解析器配置 + parserConfig: getLocalStorageData(BACKUP_KEYS.PARSER_CONFIG, {}), + // 解析器数据 + parsers: getLocalStorageData(BACKUP_KEYS.PARSERS, []), + // 聚合搜索设置 + searchAggregationSettings: getLocalStorageData(BACKUP_KEYS.SEARCH_AGGREGATION_SETTINGS, {}), + // 侧边栏状态 + sidebarCollapsed: getLocalStorageData(BACKUP_KEYS.SIDEBAR_COLLAPSED, false), + // 页面状态 + pageState: getLocalStorageData(BACKUP_KEYS.PAGE_STATE, {}) + }, + + // 用户数据 + userData: { + // 收藏列表 + favorites: getLocalStorageData(BACKUP_KEYS.FAVORITES, []), + // 观看历史(watchStatsService使用) + watchHistory: getLocalStorageData(BACKUP_KEYS.WATCH_HISTORY, []), + // 历史页面数据(historyStore使用) + histories: getLocalStorageData(BACKUP_KEYS.HISTORIES, []), + // 搜索历史记录 + searchHistory: getLocalStorageData(BACKUP_KEYS.SEARCH_HISTORY, []), + // 聚合搜索页面状态 + searchAggregationPageState: getLocalStorageData(BACKUP_KEYS.SEARCH_AGGREGATION_PAGE_STATE, {}), + // 每日统计 + dailyStats: getLocalStorageData(BACKUP_KEYS.DAILY_STATS, {}), + // 周统计 + weeklyStats: getLocalStorageData(BACKUP_KEYS.WEEKLY_STATS, {}), + // 地址配置历史 + addressHistories: collectAddressHistories() + }, + + // 站点和配置数据 + siteData: { + // 站点存储 + siteStore: getLocalStorageData(BACKUP_KEYS.SITE_STORE, {}), + // 配置地址 + configUrl: getLocalStorageData(BACKUP_KEYS.CONFIG_URL, ''), + // 直播配置地址 + liveConfigUrl: getLocalStorageData(BACKUP_KEYS.LIVE_CONFIG_URL, ''), + // 当前站点 + currentSite: getLocalStorageData(BACKUP_KEYS.CURRENT_SITE, null) + }, + + // 开发者调试设置 + debugSettings: getLocalStorageData(BACKUP_KEYS.DEBUG_SETTINGS, {}), + + // 悬浮组件数据 + floatingData: { + // 悬浮按钮位置 + buttonPosition: getLocalStorageData(BACKUP_KEYS.FLOATING_BUTTON_POSITION, null), + // 悬浮窗口位置 + windowPosition: getLocalStorageData(BACKUP_KEYS.FLOATING_WINDOW_POSITION, null), + // 悬浮窗口尺寸 + windowSize: getLocalStorageData(BACKUP_KEYS.FLOATING_WINDOW_SIZE, null) + } + } + + return backupData +} + +/** + * 验证备份数据格式 + */ +export const validateBackupData = (data) => { + try { + // 检查基本结构 + if (!data || typeof data !== 'object') { + return { valid: false, error: '备份数据格式无效' } + } + + // 检查元信息 + if (!data.meta || !data.meta.version || !data.meta.timestamp) { + return { valid: false, error: '备份文件缺少必要的元信息' } + } + + // 检查版本兼容性 + if (data.meta.version !== BACKUP_VERSION) { + console.warn(`备份文件版本 ${data.meta.version} 与当前版本 ${BACKUP_VERSION} 不匹配`) + // 暂时允许不同版本,但给出警告 + } + + // 检查必要的数据结构 + const requiredSections = ['settings', 'userData', 'siteData'] + for (const section of requiredSections) { + if (!data[section] || typeof data[section] !== 'object') { + return { valid: false, error: `备份数据缺少 ${section} 部分` } + } + } + + return { valid: true } + } catch (error) { + return { valid: false, error: '备份数据解析失败: ' + error.message } + } +} + +/** + * 导出备份数据到JSON文件 + */ +export const exportBackupData = () => { + try { + const backupData = collectBackupData() + const timestamp = getTimestamp() + const filename = `DrPlayer_backup_${timestamp}.json` + + // 创建下载链接 + const dataStr = JSON.stringify(backupData, null, 2) + const dataBlob = new Blob([dataStr], { type: 'application/json' }) + const url = URL.createObjectURL(dataBlob) + + // 创建下载元素 + const link = document.createElement('a') + link.href = url + link.download = filename + link.style.display = 'none' + + // 触发下载 + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + // 清理URL对象 + URL.revokeObjectURL(url) + + Message.success(`备份文件已导出: ${filename}`) + return { success: true, filename } + } catch (error) { + console.error('导出备份数据失败:', error) + Message.error('导出备份失败: ' + error.message) + return { success: false, error: error.message } + } +} + +/** + * 还原备份数据 + */ +export const restoreBackupData = (backupData) => { + try { + // 验证备份数据 + const validation = validateBackupData(backupData) + if (!validation.valid) { + Message.error(validation.error) + return { success: false, error: validation.error } + } + + let restoredCount = 0 + let failedCount = 0 + const errors = [] + + // 还原设置数据 + if (backupData.settings) { + for (const [key, value] of Object.entries(backupData.settings)) { + if (key === 'parsers') { + // 特殊处理解析器数据 + if (setLocalStorageData(BACKUP_KEYS.PARSERS, value)) { + restoredCount++ + } else { + failedCount++ + errors.push(`解析器数据`) + } + } else if (key === 'searchAggregationSettings') { + // 特殊处理聚合搜索设置 + if (setLocalStorageData(BACKUP_KEYS.SEARCH_AGGREGATION_SETTINGS, value)) { + restoredCount++ + } else { + failedCount++ + errors.push(`聚合搜索设置`) + } + } else { + const storageKey = Object.values(BACKUP_KEYS).find(k => k.includes(key) || key.includes(k.split('_').pop())) + if (storageKey) { + if (setLocalStorageData(storageKey, value)) { + restoredCount++ + } else { + failedCount++ + errors.push(`设置数据 ${key}`) + } + } else { + // 直接使用key名称 + if (setLocalStorageData(key, value)) { + restoredCount++ + } else { + failedCount++ + errors.push(`设置数据 ${key}`) + } + } + } + } + } + + // 还原用户数据 + if (backupData.userData) { + const userDataMapping = { + favorites: BACKUP_KEYS.FAVORITES, + watchHistory: BACKUP_KEYS.WATCH_HISTORY, + dailyStats: BACKUP_KEYS.DAILY_STATS, + weeklyStats: BACKUP_KEYS.WEEKLY_STATS, + histories: BACKUP_KEYS.HISTORIES, // 历史页面数据 + searchHistory: BACKUP_KEYS.SEARCH_HISTORY, // 搜索历史记录 + searchAggregationPageState: BACKUP_KEYS.SEARCH_AGGREGATION_PAGE_STATE // 聚合搜索页面状态 + } + + for (const [key, value] of Object.entries(backupData.userData)) { + const storageKey = userDataMapping[key] + if (storageKey && setLocalStorageData(storageKey, value)) { + restoredCount++ + } else if (key === 'addressHistories' && value) { + // 还原地址配置历史 + for (const [historyKey, historyValue] of Object.entries(value)) { + if (setLocalStorageData(historyKey, historyValue)) { + restoredCount++ + } else { + failedCount++ + errors.push(`地址配置历史 ${historyKey}`) + } + } + } else { + failedCount++ + errors.push(`用户数据 ${key}`) + } + } + } + + // 还原站点数据 + if (backupData.siteData) { + const siteDataMapping = { + siteStore: BACKUP_KEYS.SITE_STORE, + configUrl: BACKUP_KEYS.CONFIG_URL, + liveConfigUrl: BACKUP_KEYS.LIVE_CONFIG_URL, + currentSite: BACKUP_KEYS.CURRENT_SITE + } + + let restoredCurrentSite = null + + for (const [key, value] of Object.entries(backupData.siteData)) { + const storageKey = siteDataMapping[key] + if (storageKey && setLocalStorageData(storageKey, value)) { + restoredCount++ + + // 记录还原的当前站点信息 + if (key === 'currentSite' && value) { + restoredCurrentSite = value + } + } else { + failedCount++ + errors.push(`站点数据 ${key}`) + } + } + + // 如果还原了当前站点,需要同步到siteService + if (restoredCurrentSite) { + try { + // 解析当前站点数据(可能是字符串或对象) + const currentSiteData = typeof restoredCurrentSite === 'string' + ? JSON.parse(restoredCurrentSite) + : restoredCurrentSite + + if (currentSiteData && currentSiteData.key) { + // 同步到siteService(如果站点存在) + const success = siteService.setCurrentSite(currentSiteData.key) + if (success) { + console.log('已同步还原的当前站点到siteService:', currentSiteData.name) + } else { + console.warn('还原的当前站点在站点列表中不存在,可能需要重新配置:', currentSiteData.key) + } + } + } catch (error) { + console.error('同步还原的当前站点到siteService失败:', error) + } + } + } + + // 还原开发者调试设置 + if (backupData.debugSettings) { + if (setLocalStorageData(BACKUP_KEYS.DEBUG_SETTINGS, backupData.debugSettings)) { + restoredCount++ + } else { + failedCount++ + errors.push('开发者调试设置') + } + } + + // 还原悬浮组件数据 + if (backupData.floatingData) { + const floatingDataMapping = { + buttonPosition: BACKUP_KEYS.FLOATING_BUTTON_POSITION, + windowPosition: BACKUP_KEYS.FLOATING_WINDOW_POSITION, + windowSize: BACKUP_KEYS.FLOATING_WINDOW_SIZE + } + + for (const [key, value] of Object.entries(backupData.floatingData)) { + const storageKey = floatingDataMapping[key] + if (storageKey && value !== null) { + if (setLocalStorageData(storageKey, value)) { + restoredCount++ + } else { + failedCount++ + errors.push(`悬浮组件数据 ${key}`) + } + } + } + } + + // 显示还原结果 + if (failedCount === 0) { + Message.success(`数据还原成功!共还原 ${restoredCount} 项数据`) + } else { + Message.warning(`数据还原完成!成功 ${restoredCount} 项,失败 ${failedCount} 项`) + if (errors.length > 0) { + console.warn('还原失败的项目:', errors) + } + } + + return { + success: true, + restoredCount, + failedCount, + errors, + needsReload: true // 提示需要刷新页面 + } + } catch (error) { + console.error('还原备份数据失败:', error) + Message.error('还原备份失败: ' + error.message) + return { success: false, error: error.message } + } +} + +/** + * 从文件导入备份数据 + */ +export const importBackupFromFile = (file) => { + return new Promise((resolve, reject) => { + if (!file) { + reject(new Error('请选择备份文件')) + return + } + + // 检查文件类型 + if (!file.name.endsWith('.json')) { + reject(new Error('请选择JSON格式的备份文件')) + return + } + + const reader = new FileReader() + + reader.onload = (e) => { + try { + const backupData = JSON.parse(e.target.result) + const result = restoreBackupData(backupData) + resolve(result) + } catch (error) { + reject(new Error('备份文件格式错误: ' + error.message)) + } + } + + reader.onerror = () => { + reject(new Error('文件读取失败')) + } + + reader.readAsText(file) + }) +} + +/** + * 获取备份数据统计信息 + */ +export const getBackupStats = () => { + const backupData = collectBackupData() + + const stats = { + settings: Object.keys(backupData.settings).length, + favorites: backupData.userData.favorites?.length || 0, + watchHistory: backupData.userData.histories?.length || 0, // 使用历史页面数据 + parsers: backupData.settings.parsers?.length || 0, // 解析器数量 + sites: Object.keys(backupData.siteData.siteStore || {}).length, + totalSize: JSON.stringify(backupData).length + } + + return stats +} + +export default { + collectBackupData, + validateBackupData, + exportBackupData, + restoreBackupData, + importBackupFromFile, + getBackupStats +} \ No newline at end of file diff --git a/dashboard/src/services/downloadService.js b/dashboard/src/services/downloadService.js new file mode 100644 index 0000000..2e6460c --- /dev/null +++ b/dashboard/src/services/downloadService.js @@ -0,0 +1,584 @@ +import videoService from '../api/services/video.js' + +class DownloadService { + constructor() { + this.tasks = new Map() + this.isDownloading = false + this.currentTask = null + this.downloadQueue = [] + this.maxConcurrent = 3 + this.activeDownloads = new Set() + + // 从本地存储恢复任务 + this.loadTasksFromStorage() + + // 定期保存任务状态 + setInterval(() => { + this.saveTasksToStorage() + }, 5000) + } + + // 创建新的下载任务 + createTask(novelInfo, selectedChapters, settings = {}) { + const taskId = this.generateTaskId() + const task = { + id: taskId, + novelTitle: novelInfo.title, + novelId: novelInfo.id, + novelUrl: novelInfo.url, + novelAuthor: novelInfo.author || '未知', + novelDescription: novelInfo.description || '', + novelCover: novelInfo.cover || '', // 保存封面图片 + totalChapters: selectedChapters.length, + completedChapters: 0, + failedChapters: 0, + status: 'pending', // pending, downloading, paused, completed, failed + progress: 0, + createdAt: Date.now(), + updatedAt: Date.now(), + settings: { + fileName: settings.fileName || novelInfo.title, + concurrent: settings.concurrent || 3, + retryCount: settings.retryCount || 3, + chapterInterval: settings.chapterInterval || 1000, + ...settings + }, + chapters: selectedChapters.map((chapter, index) => ({ + index, + name: chapter.name || `第${index + 1}章`, + url: chapter.url, + status: 'pending', // pending, downloading, completed, failed + progress: 0, + content: '', + size: 0, + error: null, + retryCount: 0, + startTime: null, + completeTime: null + })), + error: null, + downloadedSize: 0, + totalSize: 0, + startTime: null, + completeTime: null + } + + this.tasks.set(taskId, task) + this.saveTasksToStorage() + + return task + } + + // 开始下载任务 + async startTask(taskId) { + const task = this.tasks.get(taskId) + if (!task) { + throw new Error('任务不存在') + } + + if (task.status === 'downloading') { + return + } + + task.status = 'downloading' + task.startTime = task.startTime || Date.now() // 设置开始时间 + task.updatedAt = Date.now() + this.updateTask(task) + + // 添加到下载队列 + if (!this.downloadQueue.includes(taskId)) { + this.downloadQueue.push(taskId) + } + + // 开始处理队列 + this.processDownloadQueue() + } + + // 暂停下载任务 + pauseTask(taskId) { + const task = this.tasks.get(taskId) + if (!task) return + + task.status = 'paused' + task.updatedAt = Date.now() + this.updateTask(task) + + // 从队列中移除 + const queueIndex = this.downloadQueue.indexOf(taskId) + if (queueIndex > -1) { + this.downloadQueue.splice(queueIndex, 1) + } + + // 停止正在下载的章节 + this.activeDownloads.delete(taskId) + } + + // 取消下载任务 + cancelTask(taskId) { + const task = this.tasks.get(taskId) + if (!task) return + + this.pauseTask(taskId) + + // 重置所有下载中的章节状态 + task.chapters.forEach(chapter => { + if (chapter.status === 'downloading') { + chapter.status = 'pending' + chapter.progress = 0 + chapter.startTime = null + } + }) + + task.status = 'pending' + task.progress = 0 + this.updateTask(task) + } + + // 删除下载任务 + deleteTask(taskId) { + const task = this.tasks.get(taskId) + if (!task) return + + this.cancelTask(taskId) + this.tasks.delete(taskId) + this.saveTasksToStorage() + } + + // 重试失败的章节 + async retryChapter(taskId, chapterIndex) { + const task = this.tasks.get(taskId) + if (!task) return + + const chapter = task.chapters[chapterIndex] + if (!chapter) return + + chapter.status = 'pending' + chapter.error = null + chapter.retryCount = 0 + chapter.progress = 0 + + this.updateTask(task) + + // 如果任务正在下载,立即处理这个章节 + if (task.status === 'downloading') { + this.downloadChapter(task, chapter) + } + } + + // 处理下载队列 + async processDownloadQueue() { + if (this.downloadQueue.length === 0) return + + const taskId = this.downloadQueue[0] + const task = this.tasks.get(taskId) + + if (!task || task.status !== 'downloading') { + this.downloadQueue.shift() + this.processDownloadQueue() + return + } + + this.currentTask = task + await this.downloadTask(task) + + // 任务完成后处理下一个 + this.downloadQueue.shift() + this.currentTask = null + + if (this.downloadQueue.length > 0) { + setTimeout(() => this.processDownloadQueue(), 1000) + } + } + + // 下载单个任务 + async downloadTask(task) { + const pendingChapters = task.chapters.filter(ch => ch.status === 'pending') + + if (pendingChapters.length === 0) { + this.completeTask(task) + return + } + + // 并发下载章节 + const concurrent = Math.min(task.settings.concurrent, pendingChapters.length) + const downloadPromises = [] + + for (let i = 0; i < concurrent; i++) { + downloadPromises.push(this.downloadChaptersConcurrently(task)) + } + + await Promise.all(downloadPromises) + this.completeTask(task) + } + + // 并发下载章节 + async downloadChaptersConcurrently(task) { + while (task.status === 'downloading') { + const chapter = task.chapters.find(ch => ch.status === 'pending') + if (!chapter) break + + await this.downloadChapter(task, chapter) + + // 章节间隔 + if (task.settings.chapterInterval > 0) { + await this.sleep(task.settings.chapterInterval) + } + } + } + + // 下载单个章节 + async downloadChapter(task, chapter) { + if (chapter.status !== 'pending') return + + chapter.status = 'downloading' + chapter.startTime = Date.now() + chapter.progress = 0 + this.updateTask(task) + + try { + console.log(`开始下载章节: ${chapter.name}`) + + // 调用正确的T4播放接口获取章节内容 + const parseParams = { + play: chapter.url, + flag: task.settings.flag || '', + apiUrl: task.settings.apiUrl || '', + extend: task.settings.extend || '' + } + + const parseResult = await videoService.parseEpisodePlayUrl(task.settings.module, parseParams) + console.log('章节播放解析结果:', parseResult) + + // 解析小说内容 + let novelContent = null + if (parseResult.url && parseResult.url.startsWith('novel://')) { + const novelData = parseResult.url.replace('novel://', '') + novelContent = JSON.parse(novelData) + } else { + throw new Error('无法解析小说内容,返回的不是小说格式') + } + + // 更新章节内容和状态 + chapter.content = novelContent.content || '' + chapter.size = new Blob([chapter.content]).size + chapter.status = 'completed' + chapter.progress = 100 + chapter.completeTime = Date.now() + + task.completedChapters++ + task.downloadedSize += chapter.size + + console.log(`章节下载完成: ${chapter.name}`) + } catch (error) { + console.error('下载章节失败:', error) + + chapter.status = 'failed' + chapter.error = error.message + chapter.retryCount++ + task.failedChapters++ + + // 自动重试 + if (chapter.retryCount < task.settings.retryCount) { + console.log(`章节下载失败,准备重试 (${chapter.retryCount}/${task.settings.retryCount}): ${chapter.name}`) + await this.sleep(2000) + chapter.status = 'pending' + chapter.error = null + task.failedChapters-- + } else { + console.log(`章节下载失败,已达到最大重试次数: ${chapter.name}`) + } + } + + this.updateTaskProgress(task) + this.updateTask(task) + } + + // 构造章节URL + buildChapterUrl(novelUrl, chapterUrl) { + // 如果章节URL是完整的,直接使用 + if (chapterUrl.startsWith('http')) { + return chapterUrl + } + + // 如果是相对路径,拼接基础URL + if (chapterUrl.startsWith('/')) { + const baseUrl = new URL(novelUrl).origin + return baseUrl + chapterUrl + } + + // 其他情况,基于小说URL构造 + return `${novelUrl}/${chapterUrl}` + } + + // 解析小说内容(基于BookReader.vue的逻辑) + parseNovelContent(data) { + if (typeof data === 'string') { + // 如果是novel://协议的内容 + if (data.startsWith('novel://')) { + try { + const content = decodeURIComponent(data.substring(8)) + return content + } catch (error) { + return data + } + } + return data + } + + // 如果是对象,尝试提取文本内容 + if (typeof data === 'object') { + return data.content || data.text || JSON.stringify(data) + } + + return String(data) + } + + // 完成任务 + completeTask(task) { + const allCompleted = task.chapters.every(ch => ch.status === 'completed') + const hasFailed = task.chapters.some(ch => ch.status === 'failed') + + if (allCompleted) { + task.status = 'completed' + task.completeTime = Date.now() + } else if (hasFailed) { + task.status = 'failed' + } else { + task.status = 'paused' + } + + // 计算总大小 + task.totalSize = task.chapters.reduce((total, chapter) => total + (chapter.size || 0), 0) + + task.updatedAt = Date.now() + this.updateTask(task) + } + + // 更新任务进度 + updateTaskProgress(task) { + const completedCount = task.chapters.filter(ch => ch.status === 'completed').length + task.progress = Math.round((completedCount / task.totalChapters) * 100) + task.completedChapters = completedCount + task.failedChapters = task.chapters.filter(ch => ch.status === 'failed').length + + // 实时更新已下载大小和总大小 + task.downloadedSize = task.chapters + .filter(ch => ch.status === 'completed') + .reduce((total, chapter) => total + (chapter.size || 0), 0) + task.totalSize = task.chapters.reduce((total, chapter) => total + (chapter.size || 0), 0) + } + + // 更新任务 + updateTask(task) { + task.updatedAt = Date.now() + this.tasks.set(task.id, { ...task }) + + // 触发事件通知UI更新 + this.notifyTaskUpdate(task) + } + + // 导出为TXT文件 + exportToTxt(taskId) { + const task = this.tasks.get(taskId) + if (!task) return null + + const completedChapters = task.chapters + .filter(ch => ch.status === 'completed') + .sort((a, b) => a.index - b.index) + + if (completedChapters.length === 0) { + throw new Error('没有已完成的章节可以导出') + } + + let content = `${task.novelTitle}\n\n` + + completedChapters.forEach(chapter => { + content += `${chapter.name}\n\n` + content += `${chapter.content}\n\n` + content += '---\n\n' + }) + + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }) + const url = URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = url + link.download = `${task.settings.fileName}.txt` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + URL.revokeObjectURL(url) + + return { + fileName: `${task.settings.fileName}.txt`, + size: blob.size, + chapterCount: completedChapters.length + } + } + + // 获取所有任务 + getAllTasks() { + return Array.from(this.tasks.values()).sort((a, b) => b.createdAt - a.createdAt) + } + + // 获取单个任务 + getTask(taskId) { + return this.tasks.get(taskId) + } + + // 生成TXT内容 + generateTxtContent(taskId) { + const task = this.tasks.get(taskId) + if (!task) return null + + const completedChapters = task.chapters + .filter(ch => ch.status === 'completed') + .sort((a, b) => a.index - b.index) + + if (completedChapters.length === 0) { + return null + } + + let content = `${task.novelTitle}\n\n` + + completedChapters.forEach(chapter => { + content += `${chapter.name}\n\n` + content += `${chapter.content}\n\n` + content += '---\n\n' + }) + + return content + } + + // 根据状态过滤任务 + getTasksByStatus(status) { + return this.getAllTasks().filter(task => { + if (status === 'all') return true + if (status === 'downloaded') return task.status === 'completed' + if (status === 'downloading') return task.status === 'downloading' + if (status === 'failed') return task.status === 'failed' + if (status === 'pending') return task.status === 'pending' || task.status === 'paused' + return task.status === status + }) + } + + // 获取任务统计 + getTaskStats() { + const tasks = this.getAllTasks() + return { + total: tasks.length, + completed: tasks.filter(t => t.status === 'completed').length, + downloading: tasks.filter(t => t.status === 'downloading').length, + failed: tasks.filter(t => t.status === 'failed').length, + pending: tasks.filter(t => t.status === 'pending' || t.status === 'paused').length + } + } + + // 获取储存空间统计 + getStorageStats() { + const STORAGE_LIMIT = 100 * 1024 * 1024 // 100MB 限制,与书画柜独立 + + // 计算所有下载任务的总大小 + const usedBytes = this.getAllTasks().reduce((total, task) => { + return total + (task.downloadedSize || 0) + }, 0) + + const availableBytes = Math.max(0, STORAGE_LIMIT - usedBytes) + const usagePercentage = (usedBytes / STORAGE_LIMIT) * 100 + + return { + usedBytes, + availableBytes, + totalBytes: STORAGE_LIMIT, + usagePercentage: Math.min(100, usagePercentage), + isNearLimit: usagePercentage > 80, + isOverLimit: usagePercentage >= 100, + formattedUsed: this.formatFileSize(usedBytes), + formattedAvailable: this.formatFileSize(availableBytes), + formattedTotal: this.formatFileSize(STORAGE_LIMIT) + } + } + + // 格式化文件大小 + formatFileSize(bytes) { + if (bytes === 0) return '0 B' + + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + } + + // 检查是否可以添加新任务(基于大小限制) + canAddTask(estimatedSize = 0) { + const storageStats = this.getStorageStats() + return estimatedSize <= storageStats.availableBytes + } + + // 保存任务到本地存储 + saveTasksToStorage() { + try { + const tasksData = Array.from(this.tasks.entries()) + localStorage.setItem('novel_download_tasks', JSON.stringify(tasksData)) + } catch (error) { + console.error('保存下载任务失败:', error) + } + } + + // 从本地存储加载任务 + loadTasksFromStorage() { + try { + const tasksData = localStorage.getItem('novel_download_tasks') + if (tasksData) { + const tasks = JSON.parse(tasksData) + this.tasks = new Map(tasks) + + // 重置所有下载中的任务状态 + this.tasks.forEach(task => { + if (task.status === 'downloading') { + task.status = 'paused' + task.chapters.forEach(chapter => { + if (chapter.status === 'downloading') { + chapter.status = 'pending' + chapter.progress = 0 + chapter.startTime = null + } + }) + } + }) + } + } catch (error) { + console.error('加载下载任务失败:', error) + this.tasks = new Map() + } + } + + // 生成任务ID + generateTaskId() { + return 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9) + } + + // 睡眠函数 + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + // 事件通知(可以扩展为EventEmitter) + notifyTaskUpdate(task) { + // 这里可以实现事件发布,通知UI组件更新 + if (this.onTaskUpdate) { + this.onTaskUpdate(task) + } + } + + // 设置任务更新回调 + setTaskUpdateCallback(callback) { + this.onTaskUpdate = callback + } +} + +// 创建单例实例 +export const downloadService = new DownloadService() +export default downloadService \ No newline at end of file diff --git a/dashboard/src/services/localBookService.js b/dashboard/src/services/localBookService.js new file mode 100644 index 0000000..6c6bbeb --- /dev/null +++ b/dashboard/src/services/localBookService.js @@ -0,0 +1,533 @@ +// 本地图书存储服务 +class LocalBookService { + constructor() { + this.storageKey = 'drplayer-local-books' + this.books = [] + this.listeners = [] // 事件监听器 + this.loadBooks() + } + + // 添加事件监听器 + addEventListener(listener) { + this.listeners.push(listener) + } + + // 移除事件监听器 + removeEventListener(listener) { + const index = this.listeners.indexOf(listener) + if (index > -1) { + this.listeners.splice(index, 1) + } + } + + // 触发事件 + emit(eventType, data) { + this.listeners.forEach(listener => { + try { + listener(eventType, data) + } catch (error) { + console.error('事件监听器执行失败:', error) + } + }) + } + + // 从localStorage加载本地图书数据 + loadBooks() { + try { + const stored = localStorage.getItem(this.storageKey) + if (stored) { + this.books = JSON.parse(stored) + } + } catch (error) { + console.error('加载本地图书数据失败:', error) + this.books = [] + } + } + + // 保存本地图书数据到localStorage + saveBooks() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.books)) + } catch (error) { + console.error('保存本地图书数据失败:', error) + } + } + + // 生成唯一ID + generateId() { + return 'local_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9) + } + + // 添加本地图书 + addLocalBook(bookData) { + const book = { + id: this.generateId(), + title: bookData.title, + author: bookData.author || '未知作者', + cover: bookData.cover || '', // 封面图片URL或base64 + filePath: bookData.filePath, // 本地文件路径 + fileName: bookData.fileName, // 文件名 + fileSize: bookData.fileSize || 0, // 文件大小 + content: bookData.content || '', // 文件内容(可选,用于小文件) + type: 'local', // 标识为本地图书 + category: bookData.category || '小说', // 分类 + description: bookData.description || '', + addedAt: Date.now(), + lastReadAt: null, + readingProgress: { + position: 0, // 阅读位置 + percentage: 0, // 阅读百分比 + chapter: 0, // 当前章节(如果有) + lastReadTime: null + }, + tags: bookData.tags || [], // 标签 + favorite: false, // 是否收藏 + rating: 0 // 评分 + } + + this.books.unshift(book) + this.saveBooks() + return book + } + + // 检查是否存在重复图书 + findDuplicateBook(title, author) { + return this.books.find(book => + book.title === title && book.author === author + ) + } + + // 从内容添加图书(用于下载任务导出) + addBookFromContent(bookData, options = {}) { + const { allowOverwrite = false } = options + + // 检查重复 + const existingBook = this.findDuplicateBook(bookData.title, bookData.author || '未知作者') + if (existingBook && !allowOverwrite) { + // 返回重复信息,让调用者处理 + return { + success: false, + duplicate: true, + existingBook, + message: `图书《${bookData.title}》已存在` + } + } + + // 检查存储空间限制(覆盖时不需要检查,因为不会增加总大小) + if (!existingBook || !allowOverwrite) { + const contentSize = new Blob([bookData.content]).size + if (!this.canAddBook(contentSize)) { + const storageStats = this.getStorageStats() + return { + success: false, + storageLimit: true, + message: `存储空间不足!当前已用 ${storageStats.formattedUsed},剩余 ${storageStats.formattedAvailable},需要 ${this.formatFileSize(contentSize)}` + } + } + } + + const book = { + id: existingBook && allowOverwrite ? existingBook.id : this.generateId(), + title: bookData.title, + author: bookData.author || '未知作者', + cover: bookData.cover || '', + filePath: '', // 虚拟路径,因为内容直接存储 + fileName: bookData.fileName || `${bookData.title}.txt`, + fileSize: new Blob([bookData.content]).size, + content: bookData.content, + type: 'local', + category: '小说', + description: bookData.description || '', + addedAt: bookData.addedAt || Date.now(), + lastReadAt: null, + readingProgress: { + position: 0, + percentage: 0, + chapter: 0, + lastReadTime: null + }, + tags: bookData.source === 'download' ? ['下载导入'] : [], + favorite: false, + rating: 0 + } + + if (existingBook && allowOverwrite) { + // 覆盖现有图书 + const index = this.books.findIndex(b => b.id === existingBook.id) + if (index !== -1) { + // 保留原有的阅读进度和收藏状态 + book.readingProgress = existingBook.readingProgress + book.favorite = existingBook.favorite + book.rating = existingBook.rating + book.lastReadAt = existingBook.lastReadAt + this.books[index] = book + } + } else { + // 添加新图书 + this.books.unshift(book) + } + + this.saveBooks() + + // 触发事件 + this.emit('bookAdded', { book, isOverwrite: existingBook && allowOverwrite }) + + return { + success: true, + book, + isOverwrite: existingBook && allowOverwrite + } + } + + // 从下载任务添加图书 + addBookFromDownloadTask(task) { + const book = { + id: this.generateId(), + title: task.novelTitle, + author: task.novelAuthor || '未知作者', + cover: task.novelCover || '', + filePath: '', // 将在导出TXT后设置 + fileName: `${task.novelTitle}.txt`, + fileSize: task.downloadedSize || 0, + content: '', // 将从章节内容生成 + type: 'local', + category: '小说', + description: task.novelDescription || '', + addedAt: Date.now(), + lastReadAt: null, + readingProgress: { + position: 0, + percentage: 0, + chapter: 0, + lastReadTime: null + }, + tags: ['下载导入'], + favorite: false, + rating: 0, + // 保存原始下载任务信息 + sourceTask: { + id: task.id, + totalChapters: task.totalChapters, + completedChapters: task.completedChapters, + createdAt: task.createdAt + } + } + + this.books.unshift(book) + this.saveBooks() + return book + } + + // 获取所有本地图书 + getAllBooks() { + return this.books + } + + // 根据ID获取图书 + getBookById(id) { + return this.books.find(book => book.id === id) + } + + // 根据分类获取图书 + getBooksByCategory(category) { + if (category === 'all') { + return this.books + } + return this.books.filter(book => book.category === category) + } + + // 搜索图书 + searchBooks(keyword) { + if (!keyword.trim()) { + return this.books + } + + const lowerKeyword = keyword.toLowerCase() + return this.books.filter(book => + book.title.toLowerCase().includes(lowerKeyword) || + book.author.toLowerCase().includes(lowerKeyword) || + book.description.toLowerCase().includes(lowerKeyword) || + book.tags.some(tag => tag.toLowerCase().includes(lowerKeyword)) + ) + } + + // 更新阅读进度 + updateReadingProgress(bookId, progress) { + const book = this.getBookById(bookId) + if (book) { + book.readingProgress = { + ...book.readingProgress, + ...progress, + lastReadTime: Date.now() + } + book.lastReadAt = Date.now() + this.saveBooks() + return true + } + return false + } + + // 删除图书 + deleteBook(bookId) { + const index = this.books.findIndex(book => book.id === bookId) + if (index !== -1) { + this.books.splice(index, 1) + this.saveBooks() + return true + } + return false + } + + // 更新图书信息 + updateBook(bookId, updates) { + const book = this.getBookById(bookId) + if (book) { + Object.assign(book, updates) + this.saveBooks() + return book + } + return null + } + + // 切换收藏状态 + toggleFavorite(bookId) { + const book = this.getBookById(bookId) + if (book) { + book.favorite = !book.favorite + this.saveBooks() + return book.favorite + } + return false + } + + // 设置评分 + setRating(bookId, rating) { + const book = this.getBookById(bookId) + if (book && rating >= 0 && rating <= 5) { + book.rating = rating + this.saveBooks() + return true + } + return false + } + + // 获取统计信息 + getStats() { + const total = this.books.length + const categories = {} + const recentlyRead = this.books.filter(book => book.lastReadAt).length + const favorites = this.books.filter(book => book.favorite).length + + this.books.forEach(book => { + categories[book.category] = (categories[book.category] || 0) + 1 + }) + + return { + total, + categories, + recentlyRead, + favorites + } + } + + // 获取储存空间统计 + getStorageStats() { + const STORAGE_LIMIT = 100 * 1024 * 1024 // 100MB 限制 + + // 计算所有本地图书的总大小 + const usedBytes = this.books.reduce((total, book) => { + return total + (book.fileSize || 0) + }, 0) + + const availableBytes = Math.max(0, STORAGE_LIMIT - usedBytes) + const usagePercentage = (usedBytes / STORAGE_LIMIT) * 100 + + return { + usedBytes, + availableBytes, + totalBytes: STORAGE_LIMIT, + usagePercentage: Math.min(100, usagePercentage), + isNearLimit: usagePercentage > 80, + isOverLimit: usagePercentage >= 100, + formattedUsed: this.formatFileSize(usedBytes), + formattedAvailable: this.formatFileSize(availableBytes), + formattedTotal: this.formatFileSize(STORAGE_LIMIT) + } + } + + // 格式化文件大小 + formatFileSize(bytes) { + if (bytes === 0) return '0 B' + + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + } + + // 检查是否可以添加新图书(基于大小限制) + canAddBook(contentSize) { + const storageStats = this.getStorageStats() + return contentSize <= storageStats.availableBytes + } + + // 导出图书数据 + exportBooks() { + return { + books: this.books, + exportTime: new Date().toISOString(), + version: '1.0' + } + } + + // 导入图书数据 + importBooks(data) { + try { + if (data.books && Array.isArray(data.books)) { + // 合并导入的图书,避免重复 + const existingIds = new Set(this.books.map(book => book.id)) + const newBooks = data.books.filter(book => !existingIds.has(book.id)) + + this.books.unshift(...newBooks) + this.saveBooks() + return newBooks.length + } + return 0 + } catch (error) { + console.error('导入图书数据失败:', error) + throw error + } + } + + // 删除本地图书 + deleteLocalBook(bookId) { + try { + const bookIndex = this.books.findIndex(book => book.id === bookId) + + if (bookIndex === -1) { + return { + success: false, + error: '图书不存在' + } + } + + const deletedBook = this.books[bookIndex] + + // 从数组中移除图书 + this.books.splice(bookIndex, 1) + + // 保存到本地存储 + this.saveBooks() + + // 触发删除事件 + this.emit('bookDeleted', { book: deletedBook }) + + return { + success: true, + deletedBook + } + } catch (error) { + console.error('删除本地图书失败:', error) + return { + success: false, + error: error.message + } + } + } + + // 批量删除本地图书 + deleteMultipleBooks(bookIds) { + try { + const deletedBooks = [] + + // 按ID删除图书 + bookIds.forEach(bookId => { + const bookIndex = this.books.findIndex(book => book.id === bookId) + if (bookIndex !== -1) { + deletedBooks.push(this.books[bookIndex]) + this.books.splice(bookIndex, 1) + } + }) + + if (deletedBooks.length > 0) { + // 保存到本地存储 + this.saveBooks() + + // 触发批量删除事件 + this.emit('booksDeleted', { books: deletedBooks }) + } + + return { + success: true, + deletedCount: deletedBooks.length, + deletedBooks + } + } catch (error) { + console.error('批量删除本地图书失败:', error) + return { + success: false, + error: error.message + } + } + } + + // 清空所有图书 + clearAllBooks() { + this.books = [] + this.saveBooks() + + // 触发清空事件 + this.emit('allBooksCleared') + } + + // 读取本地文件内容 + async readLocalFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = (e) => { + resolve(e.target.result) + } + reader.onerror = (e) => { + reject(new Error('文件读取失败')) + } + reader.readAsText(file, 'utf-8') + }) + } + + // 处理文件上传 + async handleFileUpload(file) { + try { + // 验证文件类型 + if (!file.name.toLowerCase().endsWith('.txt')) { + throw new Error('只支持TXT格式的文件') + } + + // 读取文件内容 + const content = await this.readLocalFile(file) + + // 提取书名(从文件名) + const title = file.name.replace(/\.txt$/i, '') + + // 创建图书数据 + const bookData = { + title, + fileName: file.name, + fileSize: file.size, + content, + filePath: file.name, // 浏览器环境下使用文件名作为路径标识 + category: '小说', + tags: ['本地导入'] + } + + return this.addLocalBook(bookData) + } catch (error) { + console.error('处理文件上传失败:', error) + throw error + } + } +} + +// 创建单例实例 +export const localBookService = new LocalBookService() +export default localBookService \ No newline at end of file diff --git a/dashboard/src/services/resetService.js b/dashboard/src/services/resetService.js new file mode 100644 index 0000000..8255d4f --- /dev/null +++ b/dashboard/src/services/resetService.js @@ -0,0 +1,226 @@ +/** + * 重置服务 + * 负责应用的出厂重置功能 + */ + +import { Message, Modal } from '@arco-design/web-vue' +import { saveCSPConfig, setGlobalReferrerPolicy } from '@/utils/csp' + +// 默认配置值 +const DEFAULT_CONFIGS = { + // 地址设置默认值 + addressSettings: { + vodConfig: '', + liveConfig: '', + proxyAccess: '', + proxyAccessEnabled: false, + proxyPlay: 'http://localhost:57572/proxy?form=base64&url=${url}&headers=${headers}&type=${type}#嗷呜', + proxyPlayEnabled: false, + proxySniff: 'http://localhost:57573/sniffer', + proxySniffEnabled: false, + snifferTimeout: 10, + apiTimeout: 30 + }, + + // 应用设置默认值 + appSettings: { + datasourceDisplay: true, + windowPreview: true, + playerType: 'ijk', + adFilter: true, + ijkCache: false, + autoLive: false, + secureDns: false, + cspBypass: true, + referrerPolicy: 'no-referrer', + searchAggregation: false // 聚合搜索功能默认关闭 + }, + + // CSP配置默认值 + cspConfig: { + enabled: true, + referrerPolicy: 'no-referrer' + }, + + // 跳过设置默认值 + skipSettings: {}, + + // 解析器配置默认值 + parserConfig: {}, + + // 页面状态默认值 + pageState: {}, + + // 聚合搜索设置默认值 + searchAggregationSettings: { + selectedSources: [] // 默认没有选中任何搜索源 + }, + + // 侧边栏折叠状态默认值 + sidebarCollapsed: false, + + // 开发者调试设置默认值 + debugSettings: { + enabled: false, + url: 'http://localhost:5757/apps/websocket', + resetting: false + } +} + +// 需要完全清空的数据键 +const CLEAR_DATA_KEYS = [ + // 用户数据 + 'drplayer-favorites', // 收藏列表 + 'drplayer_watch_history', // 观看历史 + 'drplayer_histories', // 历史页面数据 + 'drplayer_daily_stats', // 每日统计 + 'drplayer_weekly_stats', // 周统计 + 'drplayer_parsers', // 解析器数据 + + // 聚合搜索相关 + 'searchAggregationSettings', // 聚合搜索源选择设置 + 'pageState_searchAggregation', // 聚合搜索页面状态 + 'drplayer_search_history', // 搜索历史记录 + + // 站点数据 + 'siteStore', // 站点存储 + 'drplayer_config_url', // 配置地址 + 'drplayer_live_config_url', // 直播配置地址 + 'drplayer_current_site', // 当前站点 + 'drplayer_sites', // 站点列表数据 + 'site-nowSite', // 当前站点(兼容旧系统) + + // 配置数据 + 'drplayer_config_data', // 配置数据缓存 + 'drplayer_config_fetch_time', // 配置获取时间 + + // 播放器相关 + 'drplayer_preferred_player_type', // 首选播放器类型 + 'selectedParser', // 选中的解析器 + 'last-clicked-video', // 最后点击的视频 + + // 其他设置和状态 + 'sidebar-collapsed', // 侧边栏折叠状态 + + // 地址配置历史记录 + 'drplayer_vod_config_history', + 'drplayer_live_config_history', + 'drplayer_proxy_access_history', + 'drplayer_proxy_play_history', + 'drplayer_proxy_sniff_history', + + // 开发者调试设置相关 + 'debugSettings', + + // 悬浮组件相关 + 'floating-iframe-button-position', + 'floating-iframe-window-position', + 'floating-iframe-window-size' +] + +/** + * 显示重置确认对话框 + */ +export const showResetConfirmation = () => { + return new Promise((resolve) => { + Modal.confirm({ + title: '确认重置', + content: `此操作将执行完整的出厂重置,包括: + +• 重置所有配置接口地址为默认值 +• 清空收藏列表 +• 清空观看历史记录 +• 清空解析器数据 +• 清空站点数据 +• 重置所有应用设置 + +⚠️ 此操作不可撤销,请确认是否继续?`, + width: 480, + closable: true, + okText: '确认重置', + cancelText: '取消', + okButtonProps: { + status: 'danger' + }, + onOk: () => { + resolve(true) + }, + onCancel: () => { + resolve(false) + } + }) + }) +} + +/** + * 执行完整的出厂重置 + */ +export const performFactoryReset = async () => { + try { + // 1. 清空需要删除的数据 + CLEAR_DATA_KEYS.forEach(key => { + localStorage.removeItem(key) + }) + + // 2. 重置配置为默认值 + Object.entries(DEFAULT_CONFIGS).forEach(([key, defaultValue]) => { + if (defaultValue !== null && defaultValue !== undefined) { + localStorage.setItem(key, JSON.stringify(defaultValue)) + } + }) + + // 3. 重置CSP配置 + try { + saveCSPConfig(DEFAULT_CONFIGS.cspConfig) + setGlobalReferrerPolicy('no-referrer') + } catch (error) { + console.warn('CSP配置重置失败:', error) + } + + // 4. 显示成功消息 + Message.success({ + content: '出厂重置完成!应用已恢复到初始状态', + duration: 3000 + }) + + // 5. 建议用户刷新页面 + setTimeout(() => { + Modal.info({ + title: '重置完成', + content: '为确保所有更改生效,建议刷新页面。是否立即刷新?', + okText: '立即刷新', + cancelText: '稍后刷新', + onOk: () => { + window.location.reload() + } + }) + }, 1000) + + return true + } catch (error) { + console.error('出厂重置失败:', error) + Message.error({ + content: '出厂重置失败,请重试', + duration: 3000 + }) + return false + } +} + +/** + * 带确认的出厂重置函数 + */ +export const factoryResetWithConfirmation = async () => { + const confirmed = await showResetConfirmation() + if (confirmed) { + return await performFactoryReset() + } + return false +} + +export default { + showResetConfirmation, + performFactoryReset, + factoryResetWithConfirmation, + DEFAULT_CONFIGS +} \ No newline at end of file diff --git a/dashboard/src/stores/downloadStore.js b/dashboard/src/stores/downloadStore.js new file mode 100644 index 0000000..02f10d6 --- /dev/null +++ b/dashboard/src/stores/downloadStore.js @@ -0,0 +1,205 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { downloadService } from '@/services/downloadService' +import localBookService from '@/services/localBookService' +import { Message, Modal } from '@arco-design/web-vue' + +export const useDownloadStore = defineStore('download', () => { + // 任务列表 + const tasks = ref([]) + + // 加载状态 + const loading = ref(false) + + // 从downloadService加载任务 + const loadTasks = () => { + tasks.value = downloadService.getAllTasks() + } + + // 保存任务到downloadService + const saveTasks = () => { + downloadService.saveTasksToStorage() + } + + // 添加下载任务 + const addTask = (taskData) => { + const task = downloadService.createTask(taskData) + loadTasks() // 重新加载任务列表 + return task + } + + // 开始任务 + const startTask = (taskId) => { + downloadService.startTask(taskId) + loadTasks() + } + + // 暂停任务 + const pauseTask = (taskId) => { + downloadService.pauseTask(taskId) + loadTasks() + } + + // 恢复任务 + const resumeTask = (taskId) => { + downloadService.startTask(taskId) + loadTasks() + } + + // 取消任务 + const cancelTask = (taskId) => { + downloadService.cancelTask(taskId) + loadTasks() + } + + // 删除任务 + const deleteTask = (taskId) => { + downloadService.deleteTask(taskId) + loadTasks() + } + + // 重试任务 + const retryTask = (taskId) => { + downloadService.startTask(taskId) + loadTasks() + } + + // 重试章节 + const retryChapter = (taskId, chapterIndex) => { + downloadService.retryChapter(taskId, chapterIndex) + loadTasks() + } + + // 导出任务 + const exportTask = async (taskId, options = {}) => { + try { + const task = downloadService.getTask(taskId) + if (!task) { + Message.error('任务不存在') + return + } + + if (task.status !== 'completed') { + Message.error('只能导出已完成的任务') + return + } + + // 生成TXT内容 + const txtContent = downloadService.generateTxtContent(taskId) + if (!txtContent) { + Message.error('生成TXT内容失败') + return + } + + // 如果选择导出到书画柜 + if (options.exportToGallery) { + const bookData = { + title: task.novelTitle, + author: task.novelAuthor || '未知', + description: task.novelDescription || '', + cover: task.novelCover || '', + content: txtContent, + fileName: `${task.settings.fileName || task.novelTitle}.txt`, + addedAt: Date.now(), + source: 'download' + } + + // 尝试添加图书,检查是否重复 + const result = localBookService.addBookFromContent(bookData) + + if (result.success) { + const action = result.isOverwrite ? '更新' : '添加' + Message.success(`《${task.novelTitle}》已${action}到书画柜`) + return { success: true, action: 'addToGallery', isOverwrite: result.isOverwrite } + } else if (result.duplicate) { + // 显示覆盖确认对话框 + return new Promise((resolve) => { + Modal.confirm({ + title: '图书已存在', + content: `书画柜中已存在《${bookData.title}》(作者:${bookData.author}),是否要覆盖现有图书?`, + okText: '覆盖', + cancelText: '取消', + onOk: () => { + // 用户选择覆盖 + const overwriteResult = localBookService.addBookFromContent(bookData, { allowOverwrite: true }) + if (overwriteResult.success) { + Message.success(`《${task.novelTitle}》已更新到书画柜`) + resolve({ success: true, action: 'addToGallery', isOverwrite: true }) + } else { + Message.error('更新图书失败') + resolve({ success: false }) + } + }, + onCancel: () => { + Message.info('已取消导出') + resolve({ success: false, cancelled: true }) + } + }) + }) + } else if (result.storageLimit) { + // 存储空间不足 + Message.error(result.message) + return { success: false, storageLimit: true } + } else { + Message.error(result.message || '添加图书失败') + return { success: false } + } + } else { + // 默认下载TXT文件 + const result = downloadService.exportToTxt(taskId) + Message.success('TXT文件导出成功') + return result + } + } catch (error) { + console.error('导出任务失败:', error) + Message.error('导出失败: ' + error.message) + return null + } + } + + // 清理已完成的任务 + const clearCompleted = () => { + const completedTasks = tasks.value.filter(task => task.status === 'completed') + completedTasks.forEach(task => { + downloadService.deleteTask(task.id) + }) + loadTasks() + } + + // 计算属性 + const taskStats = computed(() => { + return downloadService.getTaskStats() + }) + + // 根据状态过滤任务 + const getTasksByStatus = (status) => { + return downloadService.getTasksByStatus(status) + } + + // 设置任务更新回调 + downloadService.setTaskUpdateCallback(() => { + loadTasks() + }) + + // 初始化时加载任务 + loadTasks() + + return { + tasks, + loading, + taskStats, + loadTasks, + saveTasks, + addTask, + startTask, + pauseTask, + resumeTask, + cancelTask, + deleteTask, + retryTask, + retryChapter, + exportTask, + clearCompleted, + getTasksByStatus + } +}) \ No newline at end of file diff --git a/dashboard/src/stores/favoriteStore.js b/dashboard/src/stores/favoriteStore.js new file mode 100644 index 0000000..607e193 --- /dev/null +++ b/dashboard/src/stores/favoriteStore.js @@ -0,0 +1,216 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useFavoriteStore = defineStore('favorite', () => { + // 收藏列表 + const favorites = ref([]) + + // 从localStorage加载收藏数据 + const loadFavorites = () => { + try { + const stored = localStorage.getItem('drplayer-favorites') + if (stored) { + favorites.value = JSON.parse(stored) + } + } catch (error) { + console.error('加载收藏数据失败:', error) + favorites.value = [] + } + } + + // 保存收藏数据到localStorage + const saveFavorites = () => { + try { + localStorage.setItem('drplayer-favorites', JSON.stringify(favorites.value)) + } catch (error) { + console.error('保存收藏数据失败:', error) + } + } + + // 添加收藏 + const addFavorite = (videoData) => { + const favoriteItem = { + id: videoData.vod_id, + name: videoData.vod_name, + pic: videoData.vod_pic, + year: videoData.vod_year, + area: videoData.vod_area, + type_name: videoData.type_name, + remarks: videoData.vod_remarks, + director: videoData.vod_director, + actor: videoData.vod_actor, + // 保存API调用信息,用于从收藏进入详情页 + api_info: { + module: videoData.module || '', + api_url: videoData.api_url || '', + site_name: videoData.site_name || '', + ext: videoData.ext || null // 添加站源扩展配置 + }, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + } + + // 检查是否已存在 + const existingIndex = favorites.value.findIndex(item => + item.id === favoriteItem.id && + item.api_info.api_url === favoriteItem.api_info.api_url + ) + + if (existingIndex === -1) { + favorites.value.unshift(favoriteItem) + saveFavorites() + return true + } + return false + } + + // 移除收藏 + const removeFavorite = (videoId, apiUrl) => { + const index = favorites.value.findIndex(item => + item.id === videoId && item.api_info.api_url === apiUrl + ) + + if (index !== -1) { + favorites.value.splice(index, 1) + saveFavorites() + return true + } + return false + } + + // 检查是否已收藏 + const isFavorited = (videoId, apiUrl) => { + return favorites.value.some(item => + item.id === videoId && item.api_info.api_url === apiUrl + ) + } + + // 获取收藏项 + const getFavorite = (videoId, apiUrl) => { + return favorites.value.find(item => + item.id === videoId && item.api_info.api_url === apiUrl + ) + } + + // 清空收藏 + const clearFavorites = () => { + favorites.value = [] + saveFavorites() + } + + // 导出收藏数据 + const exportFavorites = () => { + const exportData = { + version: '1.0', + export_time: new Date().toISOString(), + favorites: favorites.value + } + + const blob = new Blob([JSON.stringify(exportData, null, 2)], { + type: 'application/json' + }) + + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `drplayer-favorites-${new Date().toISOString().split('T')[0]}.json` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + + // 导入收藏数据 + const importFavorites = (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + + reader.onload = (e) => { + try { + const importData = JSON.parse(e.target.result) + + // 验证数据格式 + if (!importData.favorites || !Array.isArray(importData.favorites)) { + throw new Error('无效的收藏数据格式') + } + + // 合并数据,避免重复 + let importCount = 0 + importData.favorites.forEach(item => { + const exists = favorites.value.some(existing => + existing.id === item.id && + existing.api_info.api_url === item.api_info.api_url + ) + + if (!exists) { + favorites.value.push({ + ...item, + updated_at: new Date().toISOString() + }) + importCount++ + } + }) + + saveFavorites() + resolve(importCount) + } catch (error) { + reject(error) + } + } + + reader.onerror = () => { + reject(new Error('文件读取失败')) + } + + reader.readAsText(file) + }) + } + + // 计算属性 + const favoriteCount = computed(() => favorites.value.length) + + const favoritesByType = computed(() => { + const grouped = {} + favorites.value.forEach(item => { + // 根据站源名称中的标识进行分类 + const siteName = item.api_info?.site_name || '' + let type = '影视' // 默认分类 + + if (siteName.includes('[书]')) { + type = '小说' + } else if (siteName.includes('[画]')) { + type = '漫画' + } else if (siteName.includes('[密]')) { + type = '密' + } else if (siteName.includes('[听]')) { + type = '音频' + } else if (siteName.includes('[儿]')) { + type = '少儿' + } + + if (!grouped[type]) { + grouped[type] = [] + } + grouped[type].push(item) + }) + return grouped + }) + + // 初始化时加载数据 + loadFavorites() + + return { + favorites, + favoriteCount, + favoritesByType, + addFavorite, + removeFavorite, + isFavorited, + getFavorite, + clearFavorites, + exportFavorites, + importFavorites, + loadFavorites, + saveFavorites + } +}) \ No newline at end of file diff --git a/dashboard/src/stores/historyStore.js b/dashboard/src/stores/historyStore.js new file mode 100644 index 0000000..36a282b --- /dev/null +++ b/dashboard/src/stores/historyStore.js @@ -0,0 +1,228 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useHistoryStore = defineStore('history', () => { + // 状态 + const histories = ref([]) + + // 计算属性 + const historyCount = computed(() => histories.value.length) + + const sortedHistories = computed(() => { + return [...histories.value].sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) + }) + + const historiesByType = computed(() => { + const grouped = {} + histories.value.forEach(item => { + // 根据站源名称中的标识进行分类(与Collection.vue中的逻辑保持一致) + const siteName = item.api_info?.site_name || '' + let type = '影视' // 默认分类 + + if (siteName.includes('[书]')) { + type = '小说' + } else if (siteName.includes('[画]')) { + type = '漫画' + } else if (siteName.includes('[密]')) { + type = '密' + } else if (siteName.includes('[听]')) { + type = '音频' + } else if (siteName.includes('[儿]')) { + type = '少儿' + } + + if (!grouped[type]) { + grouped[type] = [] + } + grouped[type].push(item) + }) + return grouped + }) + + // 方法 + const loadHistories = () => { + try { + const stored = localStorage.getItem('drplayer_histories') + if (stored) { + histories.value = JSON.parse(stored) + } + } catch (error) { + console.error('加载观看历史失败:', error) + histories.value = [] + } + } + + const saveHistories = () => { + try { + localStorage.setItem('drplayer_histories', JSON.stringify(histories.value)) + } catch (error) { + console.error('保存观看历史失败:', error) + } + } + + const addToHistory = (videoInfo, routeInfo, episodeInfo) => { + const now = new Date().toISOString() + + // 调试:检查传入的videoInfo + console.log('=== historyStore.addToHistory 调试 ===') + console.log('传入的videoInfo.api_info:', videoInfo.api_info) + console.log('传入的videoInfo.api_info.ext:', videoInfo.api_info.ext) + + // 检查是否已存在相同的视频 + const existingIndex = histories.value.findIndex( + item => item.id === videoInfo.id && item.api_info.api_url === videoInfo.api_info.api_url + ) + + const historyItem = { + ...videoInfo, + current_route_name: routeInfo.name, + current_route_index: routeInfo.index, + current_episode_name: episodeInfo.name, + current_episode_index: episodeInfo.index, + current_episode_url: episodeInfo.url, + updated_at: now + } + + if (existingIndex !== -1) { + // 更新已存在的记录 + histories.value[existingIndex] = { + ...histories.value[existingIndex], + ...historyItem + } + console.log('更新后的历史记录api_info:', histories.value[existingIndex].api_info) + } else { + // 添加新记录 + historyItem.created_at = now + histories.value.push(historyItem) + console.log('新添加的历史记录api_info:', historyItem.api_info) + } + + console.log('=== historyStore.addToHistory 调试结束 ===') + saveHistories() + } + + const removeFromHistory = (item) => { + if (!item || !item.id || !item.api_info || !item.api_info.api_url) { + console.error('删除历史记录失败:参数无效', item) + return false + } + + const index = histories.value.findIndex( + h => h.id === item.id && h.api_info.api_url === item.api_info.api_url + ) + + if (index !== -1) { + histories.value.splice(index, 1) + saveHistories() + console.log('删除历史记录成功:', item.name) + return true + } else { + console.warn('未找到要删除的历史记录:', item) + return false + } + } + + const clearHistories = () => { + histories.value = [] + saveHistories() + } + + const exportHistories = () => { + const dataStr = JSON.stringify(histories.value, null, 2) + const dataBlob = new Blob([dataStr], { type: 'application/json' }) + + const link = document.createElement('a') + link.href = URL.createObjectURL(dataBlob) + link.download = `drplayer_histories_${new Date().toISOString().split('T')[0]}.json` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + + const importHistories = (jsonData) => { + try { + const importedHistories = JSON.parse(jsonData) + + if (!Array.isArray(importedHistories)) { + throw new Error('导入的数据格式不正确') + } + + // 验证数据结构 + const validHistories = importedHistories.filter(item => { + return item.id && + item.name && + item.pic && + item.api_info && + item.current_route_name && + item.current_episode_name + }) + + // 合并历史记录,避免重复 + validHistories.forEach(importedItem => { + const existingIndex = histories.value.findIndex( + item => item.id === importedItem.id && item.api_info.api_url === importedItem.api_info.api_url + ) + + if (existingIndex === -1) { + histories.value.push(importedItem) + } else { + // 如果导入的记录更新,则更新现有记录 + const existingItem = histories.value[existingIndex] + const importedTime = new Date(importedItem.updated_at) + const existingTime = new Date(existingItem.updated_at) + + if (importedTime > existingTime) { + histories.value[existingIndex] = importedItem + } + } + }) + + saveHistories() + return validHistories.length + } catch (error) { + throw new Error(`导入失败: ${error.message}`) + } + } + + const getHistoryByVideo = (videoId, apiUrl) => { + return histories.value.find( + item => item.id === videoId && item.api_info.api_url === apiUrl + ) + } + + // 获取某个视频的观看进度信息 + const getWatchProgress = (videoId, apiUrl) => { + const history = getHistoryByVideo(videoId, apiUrl) + if (!history) return null + + return { + routeName: history.current_route_name, + routeIndex: history.current_route_index, + episodeName: history.current_episode_name, + episodeIndex: history.current_episode_index, + episodeUrl: history.current_episode_url, + lastWatchTime: history.updated_at + } + } + + return { + // 状态 + histories, + + // 计算属性 + historyCount, + sortedHistories, + historiesByType, + + // 方法 + loadHistories, + saveHistories, + addToHistory, + removeFromHistory, + clearHistories, + exportHistories, + importHistories, + getHistoryByVideo, + getWatchProgress + } +}) \ No newline at end of file diff --git a/dashboard/src/stores/pageStateStore.js b/dashboard/src/stores/pageStateStore.js new file mode 100644 index 0000000..5975066 --- /dev/null +++ b/dashboard/src/stores/pageStateStore.js @@ -0,0 +1,182 @@ +import { defineStore } from 'pinia'; + +// SessionStorage键名常量 +const STORAGE_KEY = 'drplayer_page_states'; + +// 从SessionStorage加载状态 +const loadFromStorage = () => { + try { + const stored = sessionStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + console.log('🔄 [存储] 从SessionStorage加载页面状态:', parsed); + return parsed; + } + } catch (error) { + console.error('从SessionStorage加载页面状态失败:', error); + } + + // 返回默认状态 + return { + // Video页面状态 + video: { + activeKey: '', + currentPage: 1, + videos: [], + hasMore: true, + loading: false, + scrollPosition: 0, + lastUpdateTime: null + }, + // Home页面状态 + home: { + scrollPosition: 0, + lastUpdateTime: null + }, + // 搜索结果状态 + search: { + keyword: '', + currentPage: 1, + videos: [], + hasMore: true, + loading: false, + scrollPosition: 0, + lastUpdateTime: null + } + }; +}; + +// 保存到SessionStorage +const saveToStorage = (pageStates) => { + try { + sessionStorage.setItem(STORAGE_KEY, JSON.stringify(pageStates)); + console.log('🔄 [存储] 保存页面状态到SessionStorage:', pageStates); + } catch (error) { + console.error('保存页面状态到SessionStorage失败:', error); + } +}; + +export const usePageStateStore = defineStore('pageState', { + state: () => ({ + // 保存各个页面的状态,从SessionStorage初始化 + pageStates: loadFromStorage() + }), + + actions: { + // 保存页面状态 + savePageState(pageName, state) { + if (!this.pageStates[pageName]) { + this.pageStates[pageName] = {}; + } + + // 合并状态,保留时间戳 + this.pageStates[pageName] = { + ...this.pageStates[pageName], + ...state, + lastUpdateTime: Date.now() + }; + + // 立即保存到SessionStorage + saveToStorage(this.pageStates); + + console.log(`🔄 [状态保存] 页面状态 [${pageName}]:`, this.pageStates[pageName]); + }, + + // 获取页面状态 + getPageState(pageName) { + const state = this.pageStates[pageName]; + console.log(`获取页面状态 [${pageName}]:`, state); + return state || {}; + }, + + // 清除页面状态 + clearPageState(pageName) { + if (this.pageStates[pageName]) { + this.pageStates[pageName] = {}; + + // 同步到SessionStorage + saveToStorage(this.pageStates); + + console.log(`🔄 [状态清除] 页面状态 [${pageName}]`); + } + }, + + // 检查状态是否过期(超过30分钟) + isStateExpired(pageName, maxAge = 30 * 60 * 1000) { + const state = this.pageStates[pageName]; + if (!state || !state.lastUpdateTime) { + return true; + } + return Date.now() - state.lastUpdateTime > maxAge; + }, + + // 保存Video页面特定状态 + saveVideoState(activeKey, currentPage, videos, hasMore, loading, scrollPosition = 0) { + this.savePageState('video', { + activeKey, + currentPage, + videos: [...videos], // 深拷贝数组 + hasMore, + loading, + scrollPosition + }); + }, + + // 保存搜索状态 + saveSearchState(keyword, currentPage, videos, hasMore, loading, scrollPosition = 0) { + this.savePageState('search', { + keyword, + currentPage, + videos: [...videos], // 深拷贝数组 + hasMore, + loading, + scrollPosition + }); + }, + + // 保存滚动位置 + saveScrollPosition(pageName, position) { + if (this.pageStates[pageName]) { + this.pageStates[pageName].scrollPosition = position; + this.pageStates[pageName].lastUpdateTime = Date.now(); + + // 同步到SessionStorage + saveToStorage(this.pageStates); + } + }, + + // 获取滚动位置 + getScrollPosition(pageName) { + const state = this.pageStates[pageName]; + return state ? state.scrollPosition || 0 : 0; + }, + + // 重新从SessionStorage加载状态 + reloadFromStorage() { + this.pageStates = loadFromStorage(); + console.log('🔄 [存储] 重新加载页面状态:', this.pageStates); + }, + + // 清除所有SessionStorage数据 + clearAllStorage() { + try { + sessionStorage.removeItem(STORAGE_KEY); + this.pageStates = loadFromStorage(); // 重置为默认状态 + console.log('🔄 [存储] 清除所有页面状态'); + } catch (error) { + console.error('清除SessionStorage失败:', error); + } + } + }, + + getters: { + // 获取Video页面状态 + videoState: (state) => state.pageStates.video || {}, + + // 获取搜索状态 + searchState: (state) => state.pageStates.search || {}, + + // 获取Home页面状态 + homeState: (state) => state.pageStates.home || {} + } +}); \ No newline at end of file diff --git a/dashboard/src/stores/paginationStore.js b/dashboard/src/stores/paginationStore.js new file mode 100644 index 0000000..32e5bdc --- /dev/null +++ b/dashboard/src/stores/paginationStore.js @@ -0,0 +1,33 @@ +import { defineStore } from 'pinia'; + +export const usePaginationStore = defineStore('pagination', { + state: () => ({ + statsText: '', // 翻页统计文本 + isVisible: false, // 是否显示统计信息 + currentRoute: '', // 当前路由,用于判断是否显示 + }), + actions: { + // 更新翻页统计信息 + updateStats(text) { + this.statsText = text; + this.isVisible = !!text; // 有文本时显示,无文本时隐藏 + }, + // 清除翻页统计信息 + clearStats() { + this.statsText = ''; + this.isVisible = false; + }, + // 设置当前路由 + setCurrentRoute(route) { + this.currentRoute = route; + // 如果不是点播页面或搜索页面,清除统计信息 + if (route !== '/video' && route !== '/search') { + this.clearStats(); + } + } + }, + getters: { + // 是否应该显示统计信息(在点播页面或搜索页面且有统计文本) + shouldShow: (state) => state.isVisible && (state.currentRoute === '/video' || state.currentRoute === '/search') + } +}); \ No newline at end of file diff --git a/dashboard/src/stores/parser.js b/dashboard/src/stores/parser.js new file mode 100644 index 0000000..0413e1a --- /dev/null +++ b/dashboard/src/stores/parser.js @@ -0,0 +1,297 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useParserStore = defineStore('parser', () => { + // 解析列表 + const parsers = ref([]) + + // 加载状态 + const loading = ref(false) + + // 错误信息 + const error = ref(null) + + // 计算属性 + const enabledParsers = computed(() => + parsers.value.filter(parser => parser.enabled !== false) + ) + + const disabledParsers = computed(() => + parsers.value.filter(parser => parser.enabled === false) + ) + + const parserCount = computed(() => parsers.value.length) + + // 从配置地址加载解析列表 + const loadParsersFromConfig = async (configUrl) => { + loading.value = true + error.value = null + + try { + const response = await fetch(configUrl) + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const data = await response.json() + if (data.parses && Array.isArray(data.parses)) { + // 为每个解析器添加唯一ID和启用状态 + parsers.value = data.parses.map((parser, index) => ({ + ...parser, + id: parser.id || `parser_${Date.now()}_${index}`, + enabled: parser.enabled !== false, // 默认启用 + order: index + })) + + // 保存到本地存储 + saveToLocalStorage() + return true + } else { + throw new Error('配置数据格式错误:缺少parses字段') + } + } catch (err) { + error.value = err.message + console.error('加载解析配置失败:', err) + return false + } finally { + loading.value = false + } + } + + // 从本地存储加载 + const loadFromLocalStorage = () => { + try { + const stored = localStorage.getItem('drplayer_parsers') + if (stored) { + const data = JSON.parse(stored) + if (Array.isArray(data)) { + parsers.value = data + return true + } + } + } catch (err) { + console.error('从本地存储加载解析配置失败:', err) + } + return false + } + + // 保存到本地存储 + const saveToLocalStorage = () => { + try { + localStorage.setItem('drplayer_parsers', JSON.stringify(parsers.value)) + } catch (err) { + console.error('保存解析配置到本地存储失败:', err) + } + } + + // 添加解析器 + const addParser = (parser) => { + const newParser = { + ...parser, + id: `parser_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + enabled: true, + order: parsers.value.length + } + parsers.value.push(newParser) + saveToLocalStorage() + return newParser + } + + // 更新解析器 + const updateParser = (id, updates) => { + const index = parsers.value.findIndex(p => p.id === id) + if (index !== -1) { + parsers.value[index] = { ...parsers.value[index], ...updates } + saveToLocalStorage() + return true + } + return false + } + + // 删除解析器 + const deleteParser = (id) => { + const index = parsers.value.findIndex(p => p.id === id) + if (index !== -1) { + parsers.value.splice(index, 1) + saveToLocalStorage() + return true + } + return false + } + + // 切换启用状态 + const toggleParser = (id) => { + const parser = parsers.value.find(p => p.id === id) + if (parser) { + parser.enabled = !parser.enabled + saveToLocalStorage() + return parser.enabled + } + return false + } + + // 重新排序 + const reorderParsers = (newOrder) => { + parsers.value = newOrder.map((parser, index) => ({ + ...parser, + order: index + })) + saveToLocalStorage() + } + + // 根据ID映射重新排序(只更新参与拖拽的解析器) + const reorderParsersById = (orderMap) => { + // 创建一个新的解析器数组,保持原有的解析器不变 + const updatedParsers = [...parsers.value] + + // 只更新参与拖拽的解析器的order属性 + updatedParsers.forEach(parser => { + if (orderMap.has(parser.id)) { + parser.order = orderMap.get(parser.id) + } + }) + + // 按order排序 + updatedParsers.sort((a, b) => a.order - b.order) + + // 重新分配连续的order值 + updatedParsers.forEach((parser, index) => { + parser.order = index + }) + + parsers.value = updatedParsers + saveToLocalStorage() + } + + // 测试解析器 + const testParser = async (parser, testUrl) => { + try { + // 构建解析请求URL + const parseUrl = parser.url.replace(/\{url\}/g, encodeURIComponent(testUrl)) + + const response = await fetch(parseUrl, { + method: 'GET', + headers: parser.header || {}, + timeout: 10000 // 10秒超时 + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const result = await response.text() + + // 简单验证返回结果是否包含视频链接 + const hasVideoUrl = /https?:\/\/[^\s]+\.(mp4|m3u8|flv)/i.test(result) + + return { + success: true, + hasVideoUrl, + response: result, + message: hasVideoUrl ? '解析成功,检测到视频链接' : '解析完成,但未检测到视频链接' + } + } catch (err) { + return { + success: false, + error: err.message, + message: `解析失败: ${err.message}` + } + } + } + + // 导出配置 + const exportParsers = () => { + const exportData = { + parses: parsers.value.map(parser => ({ + name: parser.name, + url: parser.url, + type: parser.type, + ext: parser.ext, + header: parser.header + })), + exportTime: new Date().toISOString(), + version: '1.0' + } + + const blob = new Blob([JSON.stringify(exportData, null, 2)], { + type: 'application/json' + }) + + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `drplayer_parsers_${new Date().toISOString().split('T')[0]}.json` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + + // 导入配置 + const importParsers = async (file) => { + try { + const text = await file.text() + const data = JSON.parse(text) + + if (data.parses && Array.isArray(data.parses)) { + // 合并导入的解析器 + const importedParsers = data.parses.map((parser, index) => ({ + ...parser, + id: `imported_${Date.now()}_${index}`, + enabled: true, + order: parsers.value.length + index + })) + + parsers.value.push(...importedParsers) + saveToLocalStorage() + return { success: true, count: importedParsers.length } + } else { + throw new Error('导入文件格式错误:缺少parses字段') + } + } catch (err) { + return { success: false, error: err.message } + } + } + + // 清空所有解析器 + const clearAllParsers = () => { + parsers.value = [] + saveToLocalStorage() + } + + // 初始化时从本地存储加载 + loadFromLocalStorage() + + // loadParsers作为loadFromLocalStorage的别名,用于兼容性 + const loadParsers = () => { + loadFromLocalStorage() + } + + return { + // 状态 + parsers, + loading, + error, + + // 计算属性 + enabledParsers, + disabledParsers, + parserCount, + + // 方法 + loadParsers, + loadParsersFromConfig, + loadFromLocalStorage, + saveToLocalStorage, + addParser, + updateParser, + deleteParser, + toggleParser, + reorderParsers, + reorderParsersById, + testParser, + exportParsers, + importParsers, + clearAllParsers + } +}) \ No newline at end of file diff --git a/dashboard/src/stores/siteStore.js b/dashboard/src/stores/siteStore.js new file mode 100644 index 0000000..b82bef9 --- /dev/null +++ b/dashboard/src/stores/siteStore.js @@ -0,0 +1,55 @@ +// 在 Pinia store 中,currentSite 作为状态存储 +import {defineStore} from 'pinia' +import {ref, watch} from 'vue' +import siteService from '@/api/services/site' + +export const useSiteStore = defineStore('site', () => { + // 使用 ref 创建响应式状态,优先从 siteService 获取 + const nowSite = ref(siteService.getCurrentSite() || JSON.parse(localStorage.getItem('site-nowSite')) || null) + + // 设置当前源并同步到 siteService + const setCurrentSite = (site) => { + nowSite.value = site // 更新响应式状态 + + // 同步到两个存储系统 + localStorage.setItem('site-nowSite', JSON.stringify(site)) // 兼容旧系统 + + // 同步到 siteService(如果传入的是完整站点对象) + if (site && site.key) { + siteService.setCurrentSite(site.key) + } + + console.log('站点已切换:', site) + } + + // 从 siteService 同步站点信息 + const syncFromSiteService = () => { + const currentSite = siteService.getCurrentSite() + if (currentSite && (!nowSite.value || currentSite.key !== nowSite.value.key)) { + nowSite.value = currentSite + localStorage.setItem('site-nowSite', JSON.stringify(currentSite)) + console.log('从 siteService 同步站点:', currentSite) + } + } + + // 监听 siteService 的站点变化事件 + if (typeof window !== 'undefined') { + window.addEventListener('siteChange', (event) => { + const { site } = event.detail + if (site && (!nowSite.value || site.key !== nowSite.value.key)) { + nowSite.value = site + localStorage.setItem('site-nowSite', JSON.stringify(site)) + console.log('响应 siteService 站点变化:', site) + } + }) + } + + // 初始化时同步一次 + syncFromSiteService() + + return { + nowSite, + setCurrentSite, + syncFromSiteService, + } +}) \ No newline at end of file diff --git a/dashboard/src/stores/toast.js b/dashboard/src/stores/toast.js new file mode 100644 index 0000000..d48d8c1 --- /dev/null +++ b/dashboard/src/stores/toast.js @@ -0,0 +1,29 @@ +import { ref } from 'vue' + +// 全局Toast状态 +export const toastState = ref({ + show: false, + message: '', + type: 'success', // success, error, warning, info + duration: 3000 +}) + +// 显示Toast的方法 +export function showToast(message, type = 'success', duration = 3000) { + toastState.value = { + show: true, + message, + type, + duration + } + + // 自动隐藏 + setTimeout(() => { + hideToast() + }, duration) +} + +// 隐藏Toast的方法 +export function hideToast() { + toastState.value.show = false +} \ No newline at end of file diff --git a/dashboard/src/stores/visitedStore.js b/dashboard/src/stores/visitedStore.js new file mode 100644 index 0000000..c378ff8 --- /dev/null +++ b/dashboard/src/stores/visitedStore.js @@ -0,0 +1,63 @@ +import { defineStore } from 'pinia'; + +export const useVisitedStore = defineStore('visited', { + state: () => ({ + lastClickedVideoId: null, // 最后点击的视频ID + lastClickedVideoName: null // 最后点击的视频名称 + }), + + getters: { + // 检查是否是最后点击的视频 + isLastClicked: (state) => (videoId) => { + return state.lastClickedVideoId === videoId; + } + }, + + actions: { + // 记录最后点击的视频 + setLastClicked(videoId, videoName) { + if (!videoId) return; + + this.lastClickedVideoId = videoId; + this.lastClickedVideoName = videoName; + + // 保存到localStorage + this.saveToStorage(); + }, + + // 清除记录 + clear() { + this.lastClickedVideoId = null; + this.lastClickedVideoName = null; + localStorage.removeItem('last-clicked-video'); + }, + + // 保存到localStorage + saveToStorage() { + try { + const data = { + videoId: this.lastClickedVideoId, + videoName: this.lastClickedVideoName + }; + localStorage.setItem('last-clicked-video', JSON.stringify(data)); + } catch (error) { + console.warn('保存最后点击视频失败:', error); + } + }, + + // 从localStorage加载 + loadFromStorage() { + try { + const stored = localStorage.getItem('last-clicked-video'); + if (stored) { + const data = JSON.parse(stored); + this.lastClickedVideoId = data.videoId; + this.lastClickedVideoName = data.videoName; + } + } catch (error) { + console.warn('加载最后点击视频失败:', error); + this.clear(); + } + } + } +}); \ No newline at end of file diff --git a/dashboard/src/style.css b/dashboard/src/style.css index bb131d6..270dabe 100644 --- a/dashboard/src/style.css +++ b/dashboard/src/style.css @@ -1,3 +1,6 @@ +/* 引入现代化设计系统 */ +@import './styles/design-system.css'; + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; diff --git a/dashboard/src/styles/design-system.css b/dashboard/src/styles/design-system.css new file mode 100644 index 0000000..2b876f6 --- /dev/null +++ b/dashboard/src/styles/design-system.css @@ -0,0 +1,270 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* 现代化设计系统 - 基础变量 */ +:root { + /* 主色调 - 渐变蓝紫色系 */ + --ds-primary-50: #f0f9ff; + --ds-primary-100: #e0f2fe; + --ds-primary-200: #bae6fd; + --ds-primary-300: #7dd3fc; + --ds-primary-400: #38bdf8; + --ds-primary-500: #0ea5e9; + --ds-primary-600: #0284c7; + --ds-primary-700: #0369a1; + --ds-primary-800: #075985; + --ds-primary-900: #0c4a6e; + + /* 渐变色彩 */ + --ds-gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --ds-gradient-secondary: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + --ds-gradient-success: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + --ds-gradient-warning: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); + --ds-gradient-danger: linear-gradient(135deg, #fa709a 0%, #fee140 100%); + + /* 毛玻璃效果 */ + --ds-glass-bg: rgba(255, 255, 255, 0.1); + --ds-glass-bg-dark: rgba(0, 0, 0, 0.1); + --ds-glass-border: rgba(255, 255, 255, 0.2); + --ds-glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.37); + + /* 阴影系统 */ + --ds-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --ds-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --ds-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --ds-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --ds-shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + + /* 圆角系统 */ + --ds-radius-sm: 0.375rem; + --ds-radius-md: 0.5rem; + --ds-radius-lg: 0.75rem; + --ds-radius-xl: 1rem; + --ds-radius-2xl: 1.5rem; + + /* 间距系统 */ + --ds-space-xs: 0.25rem; + --ds-space-sm: 0.5rem; + --ds-space-md: 1rem; + --ds-space-lg: 1.5rem; + --ds-space-xl: 2rem; + --ds-space-2xl: 3rem; + + /* 字体系统 */ + --ds-font-size-xs: 0.75rem; + --ds-font-size-sm: 0.875rem; + --ds-font-size-base: 1rem; + --ds-font-size-lg: 1.125rem; + --ds-font-size-xl: 1.25rem; + --ds-font-size-2xl: 1.5rem; + + /* 动画时长 */ + --ds-duration-fast: 150ms; + --ds-duration-normal: 300ms; + --ds-duration-slow: 500ms; + + /* Z-index 层级 */ + --ds-z-dropdown: 1000; + --ds-z-modal: 1050; + --ds-z-popover: 1060; + --ds-z-tooltip: 1070; +} + +/* 毛玻璃效果基础类 */ +.glass-effect { + background: var(--ds-glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--ds-glass-border); + box-shadow: var(--ds-glass-shadow); +} + +.glass-effect-dark { + background: var(--ds-glass-bg-dark); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: var(--ds-glass-shadow); +} + +/* 渐变背景类 */ +.gradient-primary { + background: var(--ds-gradient-primary); +} + +.gradient-secondary { + background: var(--ds-gradient-secondary); +} + +.gradient-success { + background: var(--ds-gradient-success); +} + +.gradient-warning { + background: var(--ds-gradient-warning); +} + +.gradient-danger { + background: var(--ds-gradient-danger); +} + +/* 现代化按钮样式 */ +.btn-modern { + @apply relative overflow-hidden rounded-lg px-6 py-3 font-medium text-white transition-all duration-300 ease-out; + background: var(--ds-gradient-primary); + box-shadow: var(--ds-shadow-md); +} + +.btn-modern:hover { + transform: translateY(-2px); + box-shadow: var(--ds-shadow-lg); +} + +.btn-modern:active { + transform: translateY(0); + box-shadow: var(--ds-shadow-md); +} + +.btn-modern::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; +} + +.btn-modern:hover::before { + left: 100%; +} + +/* 现代化输入框样式 */ +.input-modern { + @apply w-full rounded-lg border-0 bg-white/10 px-4 py-3 text-gray-900 placeholder-gray-500 backdrop-blur-sm transition-all duration-300; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: var(--ds-shadow-sm); +} + +.input-modern:focus { + @apply outline-none ring-2 ring-blue-500/50; + background: rgba(255, 255, 255, 0.15); + box-shadow: var(--ds-shadow-md); +} + +/* 现代化卡片样式 */ +.card-modern { + @apply rounded-xl p-6 transition-all duration-300; + background: var(--ds-glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--ds-glass-border); + box-shadow: var(--ds-shadow-lg); +} + +.card-modern:hover { + transform: translateY(-4px); + box-shadow: var(--ds-shadow-xl); +} + +/* 现代化弹窗背景 */ +.modal-backdrop { + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +/* 动画类 */ +.animate-fade-in { + animation: fadeIn var(--ds-duration-normal) ease-out; +} + +.animate-slide-up { + animation: slideUp var(--ds-duration-normal) ease-out; +} + +.animate-scale-in { + animation: scaleIn var(--ds-duration-fast) ease-out; +} + +.animate-bounce-in { + animation: bounceIn var(--ds-duration-slow) ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes scaleIn { + from { + transform: scale(0.95); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes bounceIn { + 0% { + transform: scale(0.3); + opacity: 0; + } + 50% { + transform: scale(1.05); + } + 70% { + transform: scale(0.9); + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +/* 响应式设计 */ +@media (max-width: 640px) { + .card-modern { + @apply p-4; + } + + .btn-modern { + @apply px-4 py-2 text-sm; + } + + .input-modern { + @apply px-3 py-2 text-sm; + } +} + +/* 深色模式支持 */ +@media (prefers-color-scheme: dark) { + :root { + --ds-glass-bg: rgba(0, 0, 0, 0.2); + --ds-glass-border: rgba(255, 255, 255, 0.1); + } + + .input-modern { + @apply text-white placeholder-gray-400; + background: rgba(0, 0, 0, 0.2); + } + + .input-modern:focus { + background: rgba(0, 0, 0, 0.3); + } +} \ No newline at end of file diff --git a/dashboard/src/types/global.d.ts b/dashboard/src/types/global.d.ts new file mode 100644 index 0000000..035da26 --- /dev/null +++ b/dashboard/src/types/global.d.ts @@ -0,0 +1,2 @@ +// 全局类型声明 +declare const __APP_VERSION__: string \ No newline at end of file diff --git a/dashboard/src/utils/MediaPlayerManager.js b/dashboard/src/utils/MediaPlayerManager.js new file mode 100644 index 0000000..c77e688 --- /dev/null +++ b/dashboard/src/utils/MediaPlayerManager.js @@ -0,0 +1,301 @@ +import Hls from 'hls.js'; +import flvjs from 'flv.js'; +import shaka from 'shaka-player/dist/shaka-player.compiled'; +import { getCSPConfig } from '@/utils/csp'; + +// 播放器配置选项 +const playerOptions = { + hls: { + maxBufferLength: 600, // 缓冲区最大长度 + liveSyncDurationCount: 10, // 直播同步持续时间计数 + }, + flv: { + mediaDataSource: { + type: 'flv', + isLive: false, + }, + optionalConfig: { + enableWorker: false, // 启用分离线程 + enableStashBuffer: false, // 关闭IO隐藏缓冲区 + autoCleanupSourceBuffer: true, // 自动清除缓存 + reuseRedirectedURL: true, // 允许重定向请求 + fixAudioTimestampGap: false, // 音视频同步 + deferLoadAfterSourceOpen: false, // 允许延迟加载 + headers: {}, + }, + }, + dash: {}, +}; + +// 视频格式检测 +export const detectVideoFormat = (url) => { + // 确保url是字符串类型 + if (typeof url !== 'string') { + console.warn('detectVideoFormat: url must be a string, received:', typeof url, url); + return 'native'; + } + const urlLower = url.toLowerCase(); + if (urlLower.includes('.m3u8') || urlLower.includes('m3u8')) { + return 'hls'; + } else if (urlLower.includes('.flv') || urlLower.includes('flv')) { + return 'flv'; + } else if (urlLower.includes('.mpd') || urlLower.includes('mpd')) { + return 'dash'; + } else if (urlLower.includes('.ts') || urlLower.includes('ts')) { + return 'mpegts'; + } else if (urlLower.includes('magnet:') || urlLower.includes('.torrent')) { + return 'torrent'; + } + return 'native'; +}; + +// 自定义播放器创建函数 +export const createCustomPlayer = { + // HLS 播放器 + hls: (video, url, headers = {}) => { + console.log('🎬 [HLS播放器] 开始播放视频:') + console.log('📺 视频地址:', url) + console.log('📋 请求头:', headers) + + if (Hls.isSupported()) { + const options = Object.assign({}, { ...playerOptions.hls }); + + // 设置 XHR 配置,处理 referer 和自定义 headers + options.xhrSetup = function (xhr, _url) { + // 检查 CSP 绕过开关状态,只有开启时才禁用 referer 发送 + const cspConfig = getCSPConfig(); + if (cspConfig.autoBypass) { + Object.defineProperty(xhr, 'referrer', { + value: '', + writable: false + }); + } + + // 设置自定义请求头 + if (Object.keys(headers).length > 0) { + for (const key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + } + }; + + const hls = new Hls(options); + hls.loadSource(url); + hls.attachMedia(video); + return hls; + } else { + console.log('HLS is not supported.'); + return null; + } + }, + + // FLV 播放器 + flv: (video, url, headers = {}) => { + console.log('🎬 [FLV播放器] 开始播放视频:') + console.log('📺 视频地址:', url) + console.log('📋 请求头:', headers) + + if (flvjs.isSupported()) { + const flvPlayer = flvjs.createPlayer( + Object.assign({}, { ...playerOptions.flv.mediaDataSource }, { url: url }), + Object.assign({}, { ...playerOptions.flv.optionalConfig }, { headers }), + ); + flvPlayer.attachMediaElement(video); + flvPlayer.load(); + return flvPlayer; + } else { + console.log('FLV is not supported.'); + return null; + } + }, + + // DASH 播放器 + dash: (video, url, headers = {}) => { + console.log('🎬 [DASH播放器] 开始播放视频:') + console.log('📺 视频地址:', url) + console.log('📋 请求头:', headers) + + if (shaka.Player.isBrowserSupported()) { + const playerShaka = new shaka.Player(video); + + // 设置请求过滤器处理自定义请求头 + playerShaka.getNetworkingEngine().registerRequestFilter(function (type, request) { + if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) { + return; + } + for (const header in headers) { + request.headers[header] = headers[header]; + } + }); + + playerShaka.load(url); + const options = playerOptions.dash; + playerShaka.configure(options); + return playerShaka; + } else { + console.log('DASH is not supported.'); + return null; + } + }, +}; + +// 播放器切换函数 +export const switchCustomPlayer = { + hls: (video, hls, url) => { + hls.stopLoad(); + hls.detachMedia(); + + // 重新加载新的 M3U8 URL + hls.loadSource(url); + hls.attachMedia(video); + + // 等待新流解析完成并开始播放 + hls.once(Hls.Events.MANIFEST_PARSED, () => { + video.play(); + }); + return hls; + }, + + flv: (video, flv, url) => { + flv.pause(); + flv.unload(); + flv.detachMediaElement(); + flv.destroy(); + + flv = flvjs.createPlayer( + Object.assign({}, playerOptions.flv.mediaDataSource || {}, { url: url }), + playerOptions.flv.optionalConfig || {}, + ); + flv.attachMediaElement(video); + flv.load(); + return flv; + }, + + dash: (video, dash, url) => { + dash.destroy(); + const playerShaka = new shaka.Player(video); + playerShaka.load(url); + const options = playerOptions.dash; + playerShaka.configure(options); + return playerShaka; + }, +}; + +// 播放器销毁函数 +export const destroyCustomPlayer = { + hls: (player) => { + if (player?.hls) { + player.hls.destroy(); + delete player.hls; + } + }, + + flv: (player) => { + if (player?.flv) { + player.flv.destroy(); + delete player.flv; + } + }, + + dash: (player) => { + if (player?.dash) { + player.dash.destroy(); + delete player.dash; + } + }, +}; + +// 统一的播放器管理器 +export class MediaPlayerManager { + constructor(video) { + this.video = video; + this.currentPlayer = null; + this.currentFormat = 'native'; + } + + // 加载视频 + loadVideo(url, headers = {}) { + const format = detectVideoFormat(url); + + // 如果格式改变,先销毁当前播放器 + if (this.currentFormat !== format && this.currentPlayer) { + this.destroy(); + } + + this.currentFormat = format; + + switch (format) { + case 'hls': + this.currentPlayer = createCustomPlayer.hls(this.video, url, headers); + break; + case 'flv': + this.currentPlayer = createCustomPlayer.flv(this.video, url, headers); + break; + case 'dash': + this.currentPlayer = createCustomPlayer.dash(this.video, url, headers); + break; + default: + // 原生支持的格式 + console.log('🎬 [原生播放器] 开始播放视频:') + console.log('📺 视频地址:', url) + console.log('📋 请求头:', headers) + this.video.src = url; + this.currentPlayer = null; + break; + } + + return this.currentPlayer; + } + + // 切换视频源 + switchVideo(url) { + const format = detectVideoFormat(url); + + if (format === this.currentFormat && this.currentPlayer) { + // 相同格式,使用切换函数 + switch (format) { + case 'hls': + this.currentPlayer = switchCustomPlayer.hls(this.video, this.currentPlayer, url); + break; + case 'flv': + this.currentPlayer = switchCustomPlayer.flv(this.video, this.currentPlayer, url); + break; + case 'dash': + this.currentPlayer = switchCustomPlayer.dash(this.video, this.currentPlayer, url); + break; + } + } else { + // 不同格式,重新加载 + this.loadVideo(url); + } + } + + // 销毁播放器 + destroy() { + if (this.currentPlayer) { + switch (this.currentFormat) { + case 'hls': + destroyCustomPlayer.hls({ hls: this.currentPlayer }); + break; + case 'flv': + destroyCustomPlayer.flv({ flv: this.currentPlayer }); + break; + case 'dash': + destroyCustomPlayer.dash({ dash: this.currentPlayer }); + break; + } + this.currentPlayer = null; + } + this.currentFormat = 'native'; + } + + // 获取当前播放器 + getCurrentPlayer() { + return this.currentPlayer; + } + + // 获取当前格式 + getCurrentFormat() { + return this.currentFormat; + } +} \ No newline at end of file diff --git a/dashboard/src/utils/action-validator.js b/dashboard/src/utils/action-validator.js new file mode 100644 index 0000000..71276a3 --- /dev/null +++ b/dashboard/src/utils/action-validator.js @@ -0,0 +1,412 @@ +// Action组件功能验证脚本 +// 用于验证Action组件系统的各项功能是否正常工作 + +import { Actions, actionStateManager, globalConfig } from '@/components/actions' + +/** + * 验证Action组件系统的基本功能 + */ +export class ActionValidator { + constructor() { + this.results = [] + this.errors = [] + } + + /** + * 记录测试结果 + */ + log(test, success, message, data = null) { + const result = { + test, + success, + message, + data, + timestamp: new Date().toISOString() + } + + this.results.push(result) + + if (success) { + console.log(`✅ ${test}: ${message}`, data) + } else { + console.error(`❌ ${test}: ${message}`, data) + this.errors.push(result) + } + } + + /** + * 验证组件导入 + */ + async validateImports() { + try { + // 验证Actions对象 + if (typeof Actions !== 'object') { + throw new Error('Actions对象未正确导入') + } + + // 验证actionStateManager + if (typeof actionStateManager !== 'object') { + throw new Error('actionStateManager未正确导入') + } + + // 验证Actions方法 + const requiredMethods = [ + 'input', 'multiInput', 'menu', 'select', + 'msgBox', 'webView', 'help', 'alert', + 'confirm', 'info', 'success', 'warning', + 'error', 'progress' + ] + + for (const method of requiredMethods) { + if (typeof Actions[method] !== 'function') { + throw new Error(`Actions.${method}方法不存在`) + } + } + + this.log('组件导入验证', true, '所有组件和方法导入正常') + return true + } catch (error) { + this.log('组件导入验证', false, error.message, error) + return false + } + } + + /** + * 验证状态管理器 + */ + async validateStateManager() { + try { + // 验证配置设置 + const originalConfig = { ...globalConfig.value } + + actionStateManager.updateConfig({ + defaultTimeout: 5000, + debugMode: true + }) + + const newConfig = globalConfig.value + if (newConfig.defaultTimeout !== 5000 || !newConfig.debugMode) { + throw new Error('配置设置失败') + } + + // 恢复原配置 + actionStateManager.updateConfig(originalConfig) + + // 验证统计信息 + const stats = actionStateManager.statistics + if (typeof stats !== 'object') { + throw new Error('统计信息获取失败') + } + + this.log('状态管理器验证', true, '状态管理器功能正常', { stats }) + return true + } catch (error) { + this.log('状态管理器验证', false, error.message, error) + return false + } + } + + /** + * 验证基础Alert功能 + */ + async validateBasicAlert() { + try { + // 创建一个快速关闭的Alert + const alertPromise = Actions.alert('这是一个测试消息', '测试Alert') + + // 等待Alert完成 + await alertPromise + + this.log('基础Alert验证', true, 'Alert功能正常') + return true + } catch (error) { + this.log('基础Alert验证', false, error.message, error) + return false + } + } + + /** + * 验证输入组件配置 + */ + async validateInputConfig() { + try { + // 验证输入组件的配置解析 + const config = { + title: '测试输入', + message: '请输入测试内容', + placeholder: '测试占位符', + required: true, + validation: { + minLength: 3, + maxLength: 10 + }, + timeout: 1000 + } + + // 创建输入Action但立即取消 + const inputPromise = Actions.input({ + actionId: 'validator-input-test', + ...config + }) + + // 等待一小段时间后取消 + setTimeout(() => { + ActionStateManager.cancelAction() + }, 100) + + try { + await inputPromise + } catch (error) { + if (error.type === 'cancel') { + this.log('输入组件配置验证', true, '输入组件配置解析正常') + return true + } + throw error + } + + this.log('输入组件配置验证', false, '输入组件未正确取消') + return false + } catch (error) { + this.log('输入组件配置验证', false, error.message, error) + return false + } + } + + /** + * 验证多输入组件配置 + */ + async validateMultiInputConfig() { + try { + const config = { + title: '测试多输入', + message: '请填写测试信息', + inputs: [ + { + key: 'name', + label: '姓名', + required: true + }, + { + key: 'email', + label: '邮箱', + validation: { type: 'email' } + } + ], + timeout: 1000 + } + + const multiInputPromise = Actions.multiInput({ + actionId: 'validator-multi-input-test', + ...config + }) + + setTimeout(() => { + ActionStateManager.cancelAction() + }, 100) + + try { + await multiInputPromise + } catch (error) { + if (error.type === 'cancel') { + this.log('多输入组件配置验证', true, '多输入组件配置解析正常') + return true + } + throw error + } + + this.log('多输入组件配置验证', false, '多输入组件未正确取消') + return false + } catch (error) { + this.log('多输入组件配置验证', false, error.message, error) + return false + } + } + + /** + * 验证选择组件配置 + */ + async validateSelectConfig() { + try { + const config = { + title: '测试选择', + options: [ + { key: 'option1', title: '选项1' }, + { key: 'option2', title: '选项2' } + ], + multiple: false, + timeout: 1000 + } + + const selectPromise = Actions.select({ + actionId: 'validator-select-test', + ...config + }) + + setTimeout(() => { + ActionStateManager.cancelAction() + }, 100) + + try { + await selectPromise + } catch (error) { + if (error.type === 'cancel') { + this.log('选择组件配置验证', true, '选择组件配置解析正常') + return true + } + throw error + } + + this.log('选择组件配置验证', false, '选择组件未正确取消') + return false + } catch (error) { + this.log('选择组件配置验证', false, error.message, error) + return false + } + } + + /** + * 验证主题切换 + */ + async validateTheme() { + try { + const originalTheme = ActionStateManager.getConfig().theme + + // 切换到暗色主题 + ActionStateManager.setTheme('dark') + if (ActionStateManager.getConfig().theme !== 'dark') { + throw new Error('暗色主题设置失败') + } + + // 切换到亮色主题 + ActionStateManager.setTheme('light') + if (ActionStateManager.getConfig().theme !== 'light') { + throw new Error('亮色主题设置失败') + } + + // 恢复原主题 + ActionStateManager.setTheme(originalTheme) + + this.log('主题切换验证', true, '主题切换功能正常') + return true + } catch (error) { + this.log('主题切换验证', false, error.message, error) + return false + } + } + + /** + * 验证事件监听 + */ + async validateEvents() { + try { + let eventReceived = false + + // 添加事件监听器 + const removeListener = ActionStateManager.on('action:show', () => { + eventReceived = true + }) + + // 触发一个Action + const alertPromise = Actions.alert('测试事件监听', '事件测试') + + await alertPromise + + // 移除监听器 + removeListener() + + if (!eventReceived) { + throw new Error('事件未正确触发') + } + + this.log('事件监听验证', true, '事件监听功能正常') + return true + } catch (error) { + this.log('事件监听验证', false, error.message, error) + return false + } + } + + /** + * 运行所有验证测试 + */ + async runAllTests() { + console.log('🚀 开始Action组件系统验证...') + + this.results = [] + this.errors = [] + + const tests = [ + () => this.validateImports(), + () => this.validateStateManager(), + () => this.validateBasicAlert(), + () => this.validateInputConfig(), + () => this.validateMultiInputConfig(), + () => this.validateSelectConfig(), + () => this.validateTheme(), + () => this.validateEvents() + ] + + let passedTests = 0 + const totalTests = tests.length + + for (const test of tests) { + try { + const result = await test() + if (result) passedTests++ + } catch (error) { + console.error('测试执行出错:', error) + } + + // 在测试之间添加小延迟 + await new Promise(resolve => setTimeout(resolve, 100)) + } + + const summary = { + total: totalTests, + passed: passedTests, + failed: totalTests - passedTests, + success: passedTests === totalTests, + results: this.results, + errors: this.errors + } + + console.log('\n📊 验证结果汇总:') + console.log(`总测试数: ${summary.total}`) + console.log(`通过: ${summary.passed}`) + console.log(`失败: ${summary.failed}`) + console.log(`成功率: ${((summary.passed / summary.total) * 100).toFixed(1)}%`) + + if (summary.success) { + console.log('🎉 所有测试通过!Action组件系统功能正常。') + } else { + console.log('⚠️ 部分测试失败,请检查错误信息。') + console.log('错误详情:', this.errors) + } + + return summary + } + + /** + * 获取验证报告 + */ + getReport() { + return { + timestamp: new Date().toISOString(), + results: this.results, + errors: this.errors, + summary: { + total: this.results.length, + passed: this.results.filter(r => r.success).length, + failed: this.errors.length + } + } + } +} + +// 创建全局验证器实例 +export const actionValidator = new ActionValidator() + +// 便捷方法 +export const validateActionSystem = () => actionValidator.runAllTests() + +export default ActionValidator \ No newline at end of file diff --git a/dashboard/src/utils/apiUtils.js b/dashboard/src/utils/apiUtils.js new file mode 100644 index 0000000..34ab9d8 --- /dev/null +++ b/dashboard/src/utils/apiUtils.js @@ -0,0 +1,28 @@ +/** + * API相关的工具函数 + */ + +/** + * 处理extend参数 + * 如果extend是对象类型,转换为JSON字符串;如果已经是字符串,直接返回 + * @param {any} extend - extend参数 + * @returns {string|undefined} 处理后的extend参数 + */ +export const processExtendParam = (extend) => { + if (!extend) { + return undefined + } + + // 如果extend是对象类型,转换为JSON字符串 + if (typeof extend === 'object' && extend !== null) { + try { + return JSON.stringify(extend) + } catch (error) { + console.warn('extend参数JSON序列化失败:', error) + return undefined + } + } + + // 如果已经是字符串,直接返回 + return extend +} \ No newline at end of file diff --git a/dashboard/src/utils/chapterParser.js b/dashboard/src/utils/chapterParser.js new file mode 100644 index 0000000..aaecd0b --- /dev/null +++ b/dashboard/src/utils/chapterParser.js @@ -0,0 +1,278 @@ +/** + * 智能断章工具 + * 用于将txt内容解析为章节列表 + */ + +/** + * 章节标题的正则表达式模式 + */ +const CHAPTER_PATTERNS = [ + // 中文章节模式 + /^第[零一二三四五六七八九十百千万\d]+[章回节部分]/, + /^第[零一二三四五六七八九十百千万\d]+[章回节部分][\s\S]*$/, + /^[第]?[零一二三四五六七八九十百千万\d]+[、\s]*[章回节部分]/, + + // 数字章节模式 + /^第\d+章/, + /^第\d+回/, + /^第\d+节/, + /^第\d+部分/, + /^\d+[、\.\s]*[章回节部分]/, + /^Chapter\s*\d+/i, + /^Ch\.\s*\d+/i, + + // 特殊格式 + /^序章/, + /^楔子/, + /^引子/, + /^前言/, + /^后记/, + /^尾声/, + /^终章/, + /^番外/, + /^外传/, + /^附录/, + + // 英文章节模式 + /^Chapter\s+[IVX]+/i, + /^Part\s+\d+/i, + /^Section\s+\d+/i, + + // 其他常见模式 + /^【.*】$/, + /^〖.*〗$/, + /^《.*》$/, + /^「.*」$/, + /^『.*』$/ +] + +/** + * 检查一行文本是否可能是章节标题 + * @param {string} line - 要检查的文本行 + * @returns {boolean} 是否是章节标题 + */ +function isChapterTitle(line) { + const trimmedLine = line.trim() + + // 空行不是章节标题 + if (!trimmedLine) return false + + // 太长的行通常不是章节标题(超过50个字符) + if (trimmedLine.length > 50) return false + + // 检查是否匹配章节模式 + for (const pattern of CHAPTER_PATTERNS) { + if (pattern.test(trimmedLine)) { + return true + } + } + + // 检查是否是纯数字标题 + if (/^\d+$/.test(trimmedLine) && trimmedLine.length <= 3) { + return true + } + + // 检查是否是短标题(可能是章节名) + if (trimmedLine.length <= 20 && !trimmedLine.includes('。') && !trimmedLine.includes(',')) { + // 如果包含常见的章节关键词 + const chapterKeywords = ['章', '回', '节', '部', '篇', 'Chapter', 'Part'] + for (const keyword of chapterKeywords) { + if (trimmedLine.includes(keyword)) { + return true + } + } + } + + return false +} + +/** + * 解析txt内容为章节列表 + * @param {string} content - txt文件内容 + * @param {Object} options - 解析选项 + * @returns {Array} 章节列表 + */ +export function parseChapters(content, options = {}) { + const { + minChapterLength = 500, // 最小章节长度 + maxChapters = 1000, // 最大章节数 + autoDetect = true // 是否自动检测章节 + } = options + + if (!content || typeof content !== 'string') { + return [] + } + + const lines = content.split(/\r?\n/) + const chapters = [] + let currentChapter = null + let chapterIndex = 0 + + // 如果没有检测到章节标题,创建一个默认章节 + let hasChapterTitles = false + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim() + + if (autoDetect && isChapterTitle(line)) { + hasChapterTitles = true + + // 保存上一章节 + if (currentChapter && currentChapter.content.trim().length >= minChapterLength) { + chapters.push(currentChapter) + chapterIndex++ + } + + // 创建新章节 + currentChapter = { + id: chapterIndex, + title: line || `第${chapterIndex + 1}章`, + content: '', + startLine: i, + endLine: i + } + } else if (line) { + // 添加内容到当前章节 + if (!currentChapter) { + // 如果还没有章节,创建第一个章节 + currentChapter = { + id: chapterIndex, + title: `第${chapterIndex + 1}章`, + content: '', + startLine: i, + endLine: i + } + } + + if (currentChapter.content) { + currentChapter.content += '\n' + } + currentChapter.content += line + currentChapter.endLine = i + } + + // 限制章节数量 + if (chapters.length >= maxChapters) { + break + } + } + + // 添加最后一个章节 + if (currentChapter && currentChapter.content.trim().length >= minChapterLength) { + chapters.push(currentChapter) + } + + // 如果没有检测到章节标题,按长度自动分章 + if (!hasChapterTitles && content.length > minChapterLength) { + return autoSplitChapters(content, options) + } + + // 如果章节太少,尝试更宽松的检测 + if (chapters.length < 2 && content.length > minChapterLength * 2) { + return autoSplitChapters(content, options) + } + + return chapters +} + +/** + * 自动按长度分章 + * @param {string} content - 文本内容 + * @param {Object} options - 选项 + * @returns {Array} 章节列表 + */ +function autoSplitChapters(content, options = {}) { + const { + chapterLength = 3000, // 每章大约长度 + minChapterLength = 500 // 最小章节长度 + } = options + + const chapters = [] + const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim()) + + let currentChapter = { + id: 0, + title: '第1章', + content: '', + startLine: 0, + endLine: 0 + } + + let chapterIndex = 0 + + for (let i = 0; i < paragraphs.length; i++) { + const paragraph = paragraphs[i].trim() + + if (currentChapter.content.length + paragraph.length > chapterLength && + currentChapter.content.length >= minChapterLength) { + // 当前章节已经足够长,开始新章节 + chapters.push(currentChapter) + chapterIndex++ + + currentChapter = { + id: chapterIndex, + title: `第${chapterIndex + 1}章`, + content: paragraph, + startLine: i, + endLine: i + } + } else { + // 添加到当前章节 + if (currentChapter.content) { + currentChapter.content += '\n\n' + } + currentChapter.content += paragraph + currentChapter.endLine = i + } + } + + // 添加最后一个章节 + if (currentChapter.content.trim()) { + chapters.push(currentChapter) + } + + return chapters +} + +/** + * 获取章节摘要 + * @param {string} content - 章节内容 + * @param {number} maxLength - 最大长度 + * @returns {string} 摘要 + */ +export function getChapterSummary(content, maxLength = 100) { + if (!content) return '' + + const cleanContent = content.replace(/\s+/g, ' ').trim() + if (cleanContent.length <= maxLength) { + return cleanContent + } + + return cleanContent.substring(0, maxLength) + '...' +} + +/** + * 验证章节数据 + * @param {Array} chapters - 章节列表 + * @returns {boolean} 是否有效 + */ +export function validateChapters(chapters) { + if (!Array.isArray(chapters) || chapters.length === 0) { + return false + } + + for (const chapter of chapters) { + if (!chapter.title || !chapter.content || typeof chapter.id !== 'number') { + return false + } + } + + return true +} + +export default { + parseChapters, + getChapterSummary, + validateChapters, + isChapterTitle +} \ No newline at end of file diff --git a/dashboard/src/utils/csp.js b/dashboard/src/utils/csp.js new file mode 100644 index 0000000..d7d200b --- /dev/null +++ b/dashboard/src/utils/csp.js @@ -0,0 +1,192 @@ +/** + * CSP (Content Security Policy) 工具函数 + * 用于处理视频播放时的CSP策略和防盗链绕过 + */ + +/** + * Referrer 策略选项 + */ +export const REFERRER_POLICIES = { + NO_REFERRER: 'no-referrer', + NO_REFERRER_WHEN_DOWNGRADE: 'no-referrer-when-downgrade', + ORIGIN: 'origin', + ORIGIN_WHEN_CROSS_ORIGIN: 'origin-when-cross-origin', + SAME_ORIGIN: 'same-origin', + STRICT_ORIGIN: 'strict-origin', + STRICT_ORIGIN_WHEN_CROSS_ORIGIN: 'strict-origin-when-cross-origin', + UNSAFE_URL: 'unsafe-url' +} + +/** + * Referrer 策略选项列表(用于UI选择) + */ +export const REFERRER_POLICIES_LIST = [ + { value: 'no-referrer', label: '不发送Referrer' }, + { value: 'no-referrer-when-downgrade', label: 'HTTPS到HTTP时不发送' }, + { value: 'origin', label: '只发送源域名' }, + { value: 'origin-when-cross-origin', label: '跨域时只发送源域名' }, + { value: 'same-origin', label: '同源时发送完整Referrer' }, + { value: 'strict-origin', label: '严格源域名策略' }, + { value: 'strict-origin-when-cross-origin', label: '跨域时严格控制' }, + { value: 'unsafe-url', label: '总是发送完整Referrer(不安全)' } +] + +/** + * 获取当前的referrer策略 + * @returns {string} 当前的referrer策略 + */ +export function getCurrentReferrerPolicy() { + const metaTag = document.querySelector('meta[name="referrer"]') + return metaTag ? metaTag.getAttribute('content') : 'default' +} + +/** + * 设置全局referrer策略 + * @param {string} policy - referrer策略 + */ +export function setGlobalReferrerPolicy(policy) { + let metaTag = document.querySelector('meta[name="referrer"]') + + if (!metaTag) { + metaTag = document.createElement('meta') + metaTag.setAttribute('name', 'referrer') + document.head.appendChild(metaTag) + } + + metaTag.setAttribute('content', policy) + console.log(`已设置全局referrer策略为: ${policy}`) +} + +/** + * 为特定的视频元素设置referrer策略 + * @param {HTMLVideoElement} videoElement - 视频元素 + * @param {string} policy - referrer策略 + */ +export function setVideoReferrerPolicy(videoElement, policy) { + if (videoElement && videoElement.tagName === 'VIDEO') { + videoElement.setAttribute('referrerpolicy', policy) + console.log(`已为视频元素设置referrer策略: ${policy}`) + } +} + +/** + * 创建带有特定referrer策略的视频元素 + * @param {string} policy - referrer策略 + * @returns {HTMLVideoElement} 配置好的视频元素 + */ +export function createVideoWithReferrerPolicy(policy = REFERRER_POLICIES.NO_REFERRER) { + const video = document.createElement('video') + video.setAttribute('referrerpolicy', policy) + video.setAttribute('crossorigin', 'anonymous') + return video +} + +/** + * 为fetch请求设置no-referrer策略 + * @param {string} url - 请求URL + * @param {object} options - fetch选项 + * @returns {Promise} fetch请求 + */ +export function fetchWithNoReferrer(url, options = {}) { + return fetch(url, { + ...options, + referrerPolicy: REFERRER_POLICIES.NO_REFERRER + }) +} + + + +/** + * 智能设置referrer策略 + * 根据CSP绕过配置来决定referrer策略 + * @param {string} videoUrl - 视频URL + * @param {HTMLVideoElement} videoElement - 视频元素(可选) + */ +export function smartSetReferrerPolicy(videoUrl, videoElement = null) { + // 获取CSP配置 + const config = getCSPConfig() + + let policy + if (config.autoBypass) { + // 如果启用了CSP绕过,使用配置中的referrer策略 + policy = config.referrerPolicy + console.log(`根据CSP配置设置referrer策略: ${policy} (URL: ${videoUrl})`) + } else { + // 如果未启用CSP绕过,保持当前策略或使用默认策略 + const currentPolicy = getCurrentReferrerPolicy() + policy = currentPolicy || REFERRER_POLICIES.STRICT_ORIGIN_WHEN_CROSS_ORIGIN + console.log(`CSP绕过未启用,保持当前策略: ${policy} (URL: ${videoUrl})`) + } + + // 设置全局策略 + setGlobalReferrerPolicy(policy) + + // 如果提供了视频元素,也为其设置策略 + if (videoElement) { + setVideoReferrerPolicy(videoElement, policy) + } + + return policy +} + +/** + * 重置referrer策略为默认值 + */ +export function resetReferrerPolicy() { + setGlobalReferrerPolicy(REFERRER_POLICIES.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) +} + +/** + * CSP绕过配置对象 + */ +export const CSP_BYPASS_CONFIG = { + // 默认referrer策略 + referrerPolicy: REFERRER_POLICIES.NO_REFERRER, + + // 是否启用自动CSP绕过 + autoBypass: true, + + // 是否在播放失败时自动重试不同的策略 + autoRetry: true, + + // 重试策略列表 + retryPolicies: [ + REFERRER_POLICIES.NO_REFERRER, + REFERRER_POLICIES.ORIGIN, + REFERRER_POLICIES.SAME_ORIGIN, + REFERRER_POLICIES.UNSAFE_URL + ] +} + +/** + * 获取CSP绕过配置 + * @returns {object} CSP配置对象 + */ +export function getCSPConfig() { + const stored = localStorage.getItem('csp_bypass_config') + return stored ? { ...CSP_BYPASS_CONFIG, ...JSON.parse(stored) } : CSP_BYPASS_CONFIG +} + +/** + * 保存CSP绕过配置 + * @param {object} config - 配置对象 + */ +export function saveCSPConfig(config) { + localStorage.setItem('csp_bypass_config', JSON.stringify(config)) +} + +/** + * 应用CSP绕过策略到视频播放 + * @param {string} videoUrl - 视频URL + * @param {HTMLVideoElement} videoElement - 视频元素 + * @returns {string} 应用的策略 + */ +export function applyCSPBypass(videoUrl, videoElement) { + const config = getCSPConfig() + + if (!config.autoBypass) { + return getCurrentReferrerPolicy() + } + + return smartSetReferrerPolicy(videoUrl, videoElement) +} \ No newline at end of file diff --git a/dashboard/src/utils/fileTypeUtils.js b/dashboard/src/utils/fileTypeUtils.js new file mode 100644 index 0000000..026b388 --- /dev/null +++ b/dashboard/src/utils/fileTypeUtils.js @@ -0,0 +1,216 @@ +/** + * 文件类型识别工具函数 + * 根据文件名返回对应的 iconfont 图标类名 + */ + +/** + * 根据文件名获取对应的文件类型图标类名 + * @param {string} fileName - 文件名 + * @returns {string} 对应的 iconfont 图标类名 + */ +export function getFileTypeIcon(fileName) { + if (!fileName || typeof fileName !== 'string') { + return 'icon-file' + } + + // 获取文件扩展名(转为小写) + const extension = fileName.toLowerCase().split('.').pop() + + // 文档类型 + const documentTypes = { + 'doc': 'icon-file_word', + 'docx': 'icon-file_word', + 'xls': 'icon-file_excel', + 'xlsx': 'icon-file_excel', + 'ppt': 'icon-file_ppt', + 'pptx': 'icon-file_ppt', + 'pdf': 'icon-file_pdf', + 'txt': 'icon-file_txt', + 'rtf': 'icon-file_txt', + 'md': 'icon-file_txt' + } + + // 图片类型 + const imageTypes = { + 'jpg': 'icon-file_img', + 'jpeg': 'icon-file_img', + 'png': 'icon-file_img', + 'gif': 'icon-file_img', + 'bmp': 'icon-file_img', + 'svg': 'icon-file_img', + 'webp': 'icon-file_img', + 'ico': 'icon-file_img' + } + + // 视频类型 + const videoTypes = { + 'mp4': 'icon-file_video', + 'avi': 'icon-file_video', + 'mkv': 'icon-file_video', + 'mov': 'icon-file_video', + 'wmv': 'icon-file_video', + 'flv': 'icon-file_video', + 'webm': 'icon-file_video', + 'm4v': 'icon-file_video', + 'rmvb': 'icon-file_video', + 'rm': 'icon-file_video' + } + + // 音频类型 + const audioTypes = { + 'mp3': 'icon-file_music', + 'wav': 'icon-file_music', + 'flac': 'icon-file_music', + 'aac': 'icon-file_music', + 'ogg': 'icon-file_music', + 'wma': 'icon-file_music', + 'm4a': 'icon-file_music' + } + + // 压缩包类型 + const archiveTypes = { + 'zip': 'icon-file_zip', + 'rar': 'icon-file_zip', + '7z': 'icon-file_zip', + 'tar': 'icon-file_zip', + 'gz': 'icon-file_zip', + 'bz2': 'icon-file_zip' + } + + // 可执行文件类型 + const executableTypes = { + 'exe': 'icon-file_exe', + 'msi': 'icon-file_exe', + 'dmg': 'icon-file_exe', + 'pkg': 'icon-file_exe', + 'deb': 'icon-file_exe', + 'rpm': 'icon-file_exe' + } + + // 代码文件类型 + const codeTypes = { + 'html': 'icon-file_html', + 'htm': 'icon-file_html', + 'css': 'icon-file_code', + 'js': 'icon-file_code', + 'ts': 'icon-file_code', + 'vue': 'icon-file_code', + 'jsx': 'icon-file_code', + 'tsx': 'icon-file_code', + 'php': 'icon-file_code', + 'py': 'icon-file_code', + 'java': 'icon-file_code', + 'cpp': 'icon-file_code', + 'c': 'icon-file_code', + 'cs': 'icon-file_code', + 'go': 'icon-file_code', + 'rs': 'icon-file_code', + 'swift': 'icon-file_code', + 'kt': 'icon-file_code', + 'rb': 'icon-file_code', + 'json': 'icon-file_code', + 'xml': 'icon-file_code', + 'yaml': 'icon-file_code', + 'yml': 'icon-file_code' + } + + // 设计文件类型 + const designTypes = { + 'ai': 'icon-file_ai', + 'psd': 'icon-file_psd', + 'sketch': 'icon-file_psd', + 'fig': 'icon-file_psd', + 'xd': 'icon-file_psd' + } + + // CAD文件类型 + const cadTypes = { + 'dwg': 'icon-file_cad', + 'dxf': 'icon-file_cad', + 'step': 'icon-file_cad', + 'iges': 'icon-file_cad' + } + + // Flash文件类型 + const flashTypes = { + 'swf': 'icon-file_flash', + 'fla': 'icon-file_flash' + } + + // ISO文件类型 + const isoTypes = { + 'iso': 'icon-file_iso', + 'img': 'icon-file_iso', + 'bin': 'icon-file_iso' + } + + // BT种子文件 + const btTypes = { + 'torrent': 'icon-file_bt' + } + + // 云文件类型(一些云存储相关的文件) + const cloudTypes = { + 'cloud': 'icon-file_cloud', + 'gdoc': 'icon-file_cloud', + 'gsheet': 'icon-file_cloud', + 'gslides': 'icon-file_cloud' + } + + // 按优先级检查文件类型 + if (documentTypes[extension]) return documentTypes[extension] + if (imageTypes[extension]) return imageTypes[extension] + if (videoTypes[extension]) return videoTypes[extension] + if (audioTypes[extension]) return audioTypes[extension] + if (archiveTypes[extension]) return archiveTypes[extension] + if (executableTypes[extension]) return executableTypes[extension] + if (codeTypes[extension]) return codeTypes[extension] + if (designTypes[extension]) return designTypes[extension] + if (cadTypes[extension]) return cadTypes[extension] + if (flashTypes[extension]) return flashTypes[extension] + if (isoTypes[extension]) return isoTypes[extension] + if (btTypes[extension]) return btTypes[extension] + if (cloudTypes[extension]) return cloudTypes[extension] + + // 默认返回通用文件图标 + return 'icon-file' +} + +/** + * 检查项目是否为文件夹类型 + * @param {Object} item - 项目对象 + * @returns {boolean} 是否为文件夹 + */ +export function isFolder(item) { + return item && item.vod_tag && item.vod_tag.includes('folder') +} + +/** + * 检查项目是否为目录模式下的文件类型 + * @param {Object} item - 项目对象 + * @returns {boolean} 是否为目录模式下的文件 + */ +export function isDirectoryFile(item) { + return item && item.vod_tag && !item.vod_tag.includes('folder') +} + +/** + * 获取项目的显示图标类型 + * @param {Object} item - 项目对象 + * @returns {Object} 图标信息 { type: 'folder'|'file'|'image', iconClass?: string } + */ +export function getItemIconInfo(item) { + if (!item) { + return { type: 'image' } + } + + if (isFolder(item)) { + return { type: 'folder', iconClass: 'icon-wenjianjia' } + } + + if (isDirectoryFile(item)) { + return { type: 'file', iconClass: getFileTypeIcon(item.vod_name) } + } + + return { type: 'image' } +} \ No newline at end of file diff --git a/dashboard/src/utils/proxyPlayer.js b/dashboard/src/utils/proxyPlayer.js new file mode 100644 index 0000000..e870617 --- /dev/null +++ b/dashboard/src/utils/proxyPlayer.js @@ -0,0 +1,285 @@ +/** + * 代理播放地址处理工具 - 优化版本 + * 解决内存泄漏问题,提升性能 + */ +import {base64Encode} from "@/api/utils/index.js"; + +// 缓存配置 +const CACHE_CONFIG = { + MAX_SIZE: 100, // 最大缓存条目数 + CLEANUP_INTERVAL: 300000, // 5分钟清理一次 + MAX_AGE: 600000 // 缓存最大存活时间10分钟 +} + +// 全局缓存对象 +const cache = { + urlCache: new Map(), // URL编码缓存 + headerCache: new Map(), // Headers编码缓存 + proxyCache: new Map(), // 代理URL缓存 + lastCleanup: Date.now() // 上次清理时间 +} + +// 默认User-Agent(避免重复创建) +const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + +/** + * 自动清理缓存 + */ +function autoCleanupCache() { + const now = Date.now() + + // 检查是否需要清理 + if (now - cache.lastCleanup < CACHE_CONFIG.CLEANUP_INTERVAL) { + return + } + + const caches = [cache.urlCache, cache.headerCache, cache.proxyCache] + + caches.forEach(cacheMap => { + // 如果缓存超过最大大小,清理最旧的条目 + if (cacheMap.size > CACHE_CONFIG.MAX_SIZE) { + const entries = Array.from(cacheMap.entries()) + const toDelete = entries.slice(0, Math.floor(CACHE_CONFIG.MAX_SIZE * 0.3)) + toDelete.forEach(([key]) => cacheMap.delete(key)) + } + + // 清理过期条目 + for (const [key, value] of cacheMap.entries()) { + if (value.timestamp && now - value.timestamp > CACHE_CONFIG.MAX_AGE) { + cacheMap.delete(key) + } + } + }) + + cache.lastCleanup = now + + // 在开发环境下输出清理信息 + if (process.env.NODE_ENV === 'development') { + console.log('🧹 [代理缓存] 自动清理完成') + } +} + +/** + * 获取缓存的编码结果 + */ +function getCachedEncoding(text, cacheMap) { + autoCleanupCache() + + const cached = cacheMap.get(text) + if (cached) { + return cached.value + } + + const encoded = base64Encode(text) + cacheMap.set(text, { + value: encoded, + timestamp: Date.now() + }) + + return encoded +} + +/** + * 获取当前选中的代理播放地址 + * @returns {string|null} 返回代理播放地址,如果未启用代理播放则返回null + */ +export function getCurrentProxyPlayAddress() { + try { + // 从addressSettings读取代理播放配置 + const savedAddresses = JSON.parse(localStorage.getItem('addressSettings') || '{}') + const proxyPlayEnabled = savedAddresses.proxyPlayEnabled || false + const proxyPlay = savedAddresses.proxyPlay || '' + + // 如果代理播放未启用或地址为空,返回null + if (!proxyPlayEnabled || !proxyPlay || proxyPlay.trim() === '') { + return null + } + + return proxyPlay.trim() + } catch (error) { + console.error('获取代理播放地址失败:', error) + return null + } +} + +/** + * 构建代理播放URL - 优化版本 + * @param {string} originalUrl 原始视频地址 + * @param {string} proxyAddress 代理播放地址模板 + * @param {Object} headers 请求头对象 + * @returns {string} 构建后的代理播放URL + */ +export function buildProxyPlayUrl(originalUrl, proxyAddress, headers = {}) { + try { + // 创建缓存键 + const cacheKey = `${originalUrl}|${proxyAddress}|${JSON.stringify(headers)}` + + // 检查缓存 + autoCleanupCache() + const cached = cache.proxyCache.get(cacheKey) + if (cached) { + // 在开发环境下输出缓存命中信息 + if (process.env.NODE_ENV === 'development') { + console.log('🎯 [代理缓存] 缓存命中:', originalUrl.substring(0, 50) + '...') + } + return cached.value + } + + // 移除代理地址中的 #名称 部分 + const cleanProxyAddress = proxyAddress.replace(/#.*$/, '') + + // 处理默认 headers:如果 headers 为空或没有有效内容,使用默认 User-Agent + let finalHeaders = headers + if (!headers || Object.keys(headers).length === 0 || + (Object.keys(headers).length === 1 && !headers['user-agent'] && !headers['User-Agent'])) { + finalHeaders = { + 'user-agent': navigator.userAgent || DEFAULT_USER_AGENT + } + } + + // 将headers对象转换为JSON字符串 + const headersJson = JSON.stringify(finalHeaders) + + // 使用缓存的编码结果 + const encodedUrl = getCachedEncoding(originalUrl, cache.urlCache) + const encodedHeaders = getCachedEncoding(headersJson, cache.headerCache) + + // 提取文件类型(优化字符串操作) + const urlParts = originalUrl.split('/') + const lastPart = urlParts[urlParts.length - 1] + const encodedType = lastPart.split('?')[0] + + // 替换模板字符串中的变量 + const proxyUrl = cleanProxyAddress + .replace(/\$\{url\}/g, encodeURIComponent(encodedUrl)) + .replace(/\$\{headers\}/g, encodeURIComponent(encodedHeaders)) + .replace(/\$\{type\}/g, encodedType) + + // 缓存结果 + cache.proxyCache.set(cacheKey, { + value: proxyUrl, + timestamp: Date.now() + }) + + // 输出详细的调试日志 + console.log('🔄 [代理播放] 构建代理URL:') + console.log('📺 原始地址:', originalUrl) + console.log('📋 原始请求头:', headers) + console.log('📋 最终请求头:', finalHeaders) + console.log('🌐 代理模板:', proxyAddress) + console.log('🧹 清理后模板:', cleanProxyAddress) + console.log('🔐 编码后URL:', encodedUrl) + console.log('🔐 编码后Headers:', encodedHeaders) + console.log('🔗 最终代理URL:', proxyUrl) + + return proxyUrl + } catch (error) { + console.error('构建代理播放URL失败:', error) + return originalUrl // 失败时返回原始地址 + } +} + +/** + * 处理视频地址,如果启用了代理播放则返回代理URL,否则返回原始URL + * @param {string} originalUrl 原始视频地址 + * @param {Object} headers 请求头对象 + * @returns {string} 处理后的视频地址 + */ +export function processVideoUrl(originalUrl, headers = {}) { + // 获取当前代理播放地址 + const proxyAddress = getCurrentProxyPlayAddress() + + // 如果没有启用代理播放,直接返回原始地址 + if (!proxyAddress) { + // 仅在开发环境下输出日志 + if (process.env.NODE_ENV === 'development') { + console.log('🎬 [直接播放] 使用原始地址:', originalUrl.substring(0, 100) + '...') + } + return originalUrl + } + + // 构建并返回代理播放URL + return buildProxyPlayUrl(originalUrl, proxyAddress, headers) +} + +/** + * 检查是否启用了代理播放 + * @returns {boolean} 是否启用代理播放 + */ +export function isProxyPlayEnabled() { + try { + const savedAddresses = JSON.parse(localStorage.getItem('addressSettings') || '{}') + const proxyPlayEnabled = savedAddresses.proxyPlayEnabled || false + const proxyPlay = savedAddresses.proxyPlay || '' + + return proxyPlayEnabled && proxyPlay && proxyPlay.trim() !== '' + } catch (error) { + console.error('检查代理播放状态失败:', error) + return false + } +} + +/** + * 手动清理所有缓存 + * @returns {Object} 清理统计信息 + */ +export function clearProxyCache() { + const stats = { + urlCache: cache.urlCache.size, + headerCache: cache.headerCache.size, + proxyCache: cache.proxyCache.size, + totalCleared: 0 + } + + // 清理所有缓存 + cache.urlCache.clear() + cache.headerCache.clear() + cache.proxyCache.clear() + cache.lastCleanup = Date.now() + + stats.totalCleared = stats.urlCache + stats.headerCache + stats.proxyCache + + // 在开发环境下输出清理信息 + if (process.env.NODE_ENV === 'development') { + console.log('🧹 [代理缓存] 手动清理完成:', stats) + } + + return stats +} + +/** + * 获取缓存统计信息 + * @returns {Object} 缓存统计信息 + */ +export function getCacheStats() { + return { + urlCache: cache.urlCache.size, + headerCache: cache.headerCache.size, + proxyCache: cache.proxyCache.size, + lastCleanup: new Date(cache.lastCleanup).toLocaleString(), + maxSize: CACHE_CONFIG.MAX_SIZE, + maxAge: CACHE_CONFIG.MAX_AGE / 1000 + 's', + cleanupInterval: CACHE_CONFIG.CLEANUP_INTERVAL / 1000 + 's' + } +} + +/** + * 配置缓存参数 + * @param {Object} config 缓存配置 + */ +export function configureCacheSettings(config = {}) { + if (config.maxSize && config.maxSize > 0) { + CACHE_CONFIG.MAX_SIZE = config.maxSize + } + if (config.maxAge && config.maxAge > 0) { + CACHE_CONFIG.MAX_AGE = config.maxAge + } + if (config.cleanupInterval && config.cleanupInterval > 0) { + CACHE_CONFIG.CLEANUP_INTERVAL = config.cleanupInterval + } + + // 在开发环境下输出配置信息 + if (process.env.NODE_ENV === 'development') { + console.log('⚙️ [代理缓存] 配置已更新:', CACHE_CONFIG) + } +} \ No newline at end of file diff --git a/dashboard/src/utils/req.js b/dashboard/src/utils/req.js new file mode 100644 index 0000000..5fd6c44 --- /dev/null +++ b/dashboard/src/utils/req.js @@ -0,0 +1,53 @@ +import axios from 'axios'; + +// 创建一个 Axios 实例 +const req = axios.create({ + baseURL: 'http://127.0.0.1:9978', // 默认的 API 根地址 + timeout: 5000, // 请求超时限制 + headers: { + 'Content-Type': 'application/json', // 默认请求头 + }, +}); + +// 请求拦截器 +req.interceptors.request.use( + (config) => { + // 可以在请求发送前做一些处理,例如添加 token + const token = localStorage.getItem('token'); // 假设 token 存储在 localStorage 中 + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +req.interceptors.response.use( + (response) => { + // 如果有需要,可以在这里统一处理响应数据 + return response.data; + }, + (error) => { + // 错误处理,例如 token 失效、网络错误等 + if (error.response) { + // 服务器返回的错误信息 + console.error(`Error ${error.response.status}: ${error.response.data.message}`); + } else { + // 网络或其他错误 + console.error('Network error or timeout', error); + } + return Promise.reject(error); + } +); + +if (process.env.NODE_ENV === 'development') { + req.defaults.baseURL = 'http://127.0.0.1:9978'; // 开发环境使用本地服务器 +} else { + req.defaults.baseURL = ''; // 生产环境使用线上 API +} + + +export default req; diff --git a/dashboard/src/views/ActionDebugTest.vue b/dashboard/src/views/ActionDebugTest.vue new file mode 100644 index 0000000..e3d5ddb --- /dev/null +++ b/dashboard/src/views/ActionDebugTest.vue @@ -0,0 +1,786 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/ActionTest.vue b/dashboard/src/views/ActionTest.vue new file mode 100644 index 0000000..9881fb1 --- /dev/null +++ b/dashboard/src/views/ActionTest.vue @@ -0,0 +1,1951 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/BookGallery.vue b/dashboard/src/views/BookGallery.vue new file mode 100644 index 0000000..d1d91c1 --- /dev/null +++ b/dashboard/src/views/BookGallery.vue @@ -0,0 +1,884 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/CSPTest.vue b/dashboard/src/views/CSPTest.vue new file mode 100644 index 0000000..9959edf --- /dev/null +++ b/dashboard/src/views/CSPTest.vue @@ -0,0 +1,818 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/Collection.vue b/dashboard/src/views/Collection.vue index 26546b5..81f6dc4 100644 --- a/dashboard/src/views/Collection.vue +++ b/dashboard/src/views/Collection.vue @@ -1,35 +1,564 @@ - diff --git a/dashboard/src/views/History.vue b/dashboard/src/views/History.vue index 7f9dd40..4b0ee1d 100644 --- a/dashboard/src/views/History.vue +++ b/dashboard/src/views/History.vue @@ -1,16 +1,586 @@ - diff --git a/dashboard/src/views/Home.vue b/dashboard/src/views/Home.vue index 47d5d2c..fd4363c 100644 --- a/dashboard/src/views/Home.vue +++ b/dashboard/src/views/Home.vue @@ -1,16 +1,1282 @@ - +.home-container { + height: 100%; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + min-height: 100%; /* 使用100%最小高度,继承父容器 */ +} + +.dashboard-header { + position: sticky; + top: 0; + z-index: 100; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--color-border-2); + padding: 20px 0; /* 移除左右padding,让内容完全填充 */ + margin: 0 -24px; /* 抵消Layout的padding */ +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + max-width: none; /* 移除最大宽度限制 */ + margin: 0; + padding: 0 24px; /* 在这里添加左右padding */ +} + +.welcome-section { + flex: 1; +} + +.dashboard-title { + font-size: 28px; + font-weight: 600; + color: var(--color-text-1); + margin: 0 0 8px 0; + display: flex; + align-items: center; + gap: 12px; +} + +.title-icon { + color: var(--color-primary-6); + font-size: 32px; +} + +.dashboard-subtitle { + font-size: 14px; + color: var(--color-text-3); + margin: 0; +} + +.quick-stats { + display: flex; + gap: 32px; + align-items: center; +} + +.dashboard-content { + flex: 1; + overflow-y: auto; + padding: 24px 0; /* 移除左右padding */ + margin: 0 -24px; /* 抵消Layout的padding */ +} + +.content-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 24px; + max-width: none; /* 移除最大宽度限制 */ + margin: 0; + padding: 0 24px; /* 在这里添加左右padding */ +} + +.dashboard-card { + background: white; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + border: 1px solid var(--color-border-2); + overflow: hidden; + transition: all 0.3s ease; +} + +.dashboard-card:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.card-header { + padding: 20px 24px 16px; + border-bottom: 1px solid var(--color-border-2); + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + font-size: 16px; + font-weight: 600; + color: var(--color-text-1); + margin: 0; + display: flex; + align-items: center; + gap: 8px; +} + +.card-icon { + color: var(--color-primary-6); + font-size: 18px; +} + +.card-content { + padding: 20px 24px 24px; +} + +/* 观看统计卡片 */ +.watch-stats-card { + grid-column: span 2; +} + +.stat-item { + text-align: center; + min-width: 80px; +} + +.stat-value { + font-size: 24px; + font-weight: 600; + color: var(--color-text-1); + line-height: 1; + margin-bottom: 4px; +} + +.stat-value.positive { + color: var(--color-success-6); +} + +.stat-value.negative { + color: var(--color-danger-6); +} + +.stat-label { + font-size: 12px; + color: var(--color-text-3); + line-height: 1; +} + +.chart { + width: 100%; + height: 300px; +} + +/* 更新日志卡片 */ +.update-log-card { + grid-column: span 1; +} + +.timeline-content { + margin-bottom: 16px; +} + +.timeline-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.version-tag { + background: var(--color-primary-1); + color: var(--color-primary-6); + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.update-date { + font-size: 12px; + color: var(--color-text-3); +} + +.update-title { + font-size: 14px; + font-weight: 500; + color: var(--color-text-1); + margin: 0 0 8px 0; +} + +.update-description { + font-size: 13px; + color: var(--color-text-2); + margin: 0 0 12px 0; + line-height: 1.5; +} + +.update-changes { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; +} + +.more-changes { + font-size: 12px; + color: var(--color-text-3); +} + +/* 推荐卡片 */ +.recommend-card { + grid-column: span 2; +} + +.recommend-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 16px; +} + +.recommend-item { + cursor: pointer; + border-radius: 8px; + overflow: hidden; + transition: all 0.3s ease; + background: var(--color-bg-2); +} + +.recommend-item:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); +} + +.recommend-poster { + position: relative; + width: 100%; + height: 120px; + overflow: hidden; +} + +.poster-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + font-weight: 600; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); +} + +.recommend-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.3s ease; +} + +.recommend-item:hover .recommend-overlay { + opacity: 1; +} + +.play-icon { + color: white; + font-size: 32px; +} + +.trending-badge { + position: absolute; + top: 8px; + right: 8px; + background: linear-gradient(45deg, #ff6b6b, #ff8e53); + color: white; + padding: 2px 6px; + border-radius: 4px; + font-size: 10px; + font-weight: 500; +} + +.recommend-info { + padding: 12px; +} + +.recommend-title { + font-size: 14px; + font-weight: 500; + color: var(--color-text-1); + margin: 0 0 8px 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.recommend-meta { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.recommend-rating { + display: flex; + align-items: center; + gap: 2px; + font-size: 12px; + color: var(--color-text-2); +} + +.recommend-tags { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +/* 热搜关键词卡片 */ +.keywords-card { + grid-column: span 1; +} + +.keywords-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.keyword-item { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; +} + +.keyword-item:hover { + background: var(--color-bg-2); +} + +.keyword-rank { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; + background: var(--color-fill-3); + color: var(--color-text-2); +} + +.keyword-rank.top-three { + background: linear-gradient(45deg, #ff6b6b, #ff8e53); + color: white; +} + +.keyword-content { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; +} + +.keyword-text { + font-size: 14px; + color: var(--color-text-1); + font-weight: 500; +} + +.keyword-meta { + display: flex; + align-items: center; + gap: 8px; +} + +.keyword-count { + font-size: 12px; + color: var(--color-text-3); +} + +.keyword-trend { + display: flex; + align-items: center; +} + +.keyword-trend.up { + color: var(--color-success-6); +} + +.keyword-trend.down { + color: var(--color-danger-6); +} + +.keyword-trend.stable { + color: var(--color-text-3); +} + +/* 系统状态卡片 */ +.system-status-card { + grid-column: span 1; +} + +.status-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.status-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border-radius: 8px; + background: var(--color-bg-2); +} + +.status-icon { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.status-icon.online { + background: var(--color-success-1); + color: var(--color-success-6); +} + +.status-icon.warning { + background: var(--color-warning-1); + color: var(--color-warning-6); +} + +.status-icon.error { + background: var(--color-danger-1); + color: var(--color-danger-6); +} + +.status-info { + flex: 1; +} + +.status-label { + font-size: 12px; + color: var(--color-text-3); + margin-bottom: 2px; +} + +.status-value { + font-size: 14px; + font-weight: 500; + color: var(--color-text-1); +} + +/* 响应式设计 */ +@media (max-width: 1200px) { + .content-grid { + grid-template-columns: 1fr; + } + + .watch-stats-card, + .recommend-card { + grid-column: span 1; + } + + .quick-stats { + gap: 20px; + } +} + +@media (max-width: 768px) { + .dashboard-header { + padding: 16px 0; + } + + .header-content { + flex-direction: column; + gap: 16px; + align-items: flex-start; + padding: 0 20px; + } + + .quick-stats { + align-self: stretch; + justify-content: space-around; + } + + .dashboard-content { + padding: 16px 0; + } + + .content-grid { + padding: 0 20px; + } + + .content-grid { + gap: 16px; + } + + .card-header, + .card-content { + padding: 16px 20px; + } + + .recommend-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 12px; + } + + .status-grid { + grid-template-columns: 1fr; + gap: 12px; + } +} + +/* 弹窗样式 */ +.update-log-modal { + max-height: 500px; + overflow-y: auto; +} + +.update-log-modal .timeline-content { + padding-bottom: 20px; +} + +.update-log-modal .timeline-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +} + +.update-log-modal .type-tag { + margin-left: auto; +} + +.update-log-modal .update-title { + margin: 8px 0; + font-size: 16px; + font-weight: 600; + color: var(--color-text-1); +} + +.update-log-modal .update-description { + margin: 8px 0; + color: var(--color-text-2); + line-height: 1.5; +} + +.update-log-modal .update-changes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} + +.update-log-modal .change-tag { + margin: 0; +} + +.keywords-modal { + max-height: 500px; + overflow-y: auto; +} + +.keywords-modal .keywords-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.keywords-modal .keyword-item { + display: flex; + align-items: center; + gap: 16px; + padding: 12px 16px; + border-radius: 8px; + background: var(--color-bg-2); + cursor: pointer; + transition: all 0.2s ease; +} + +.keywords-modal .keyword-item:hover { + background: var(--color-bg-3); + transform: translateY(-1px); +} + +.keywords-modal .keyword-rank { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + background: var(--color-fill-3); + color: var(--color-text-2); +} + +.keywords-modal .keyword-rank.top-three { + background: linear-gradient(135deg, #ff6b6b, #ffa726); + color: white; +} + +.keywords-modal .keyword-content { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; +} + +.keywords-modal .keyword-text { + font-size: 16px; + font-weight: 500; + color: var(--color-text-1); +} + +.keywords-modal .keyword-meta { + display: flex; + align-items: center; + gap: 12px; +} + +.keywords-modal .keyword-count { + font-size: 14px; + color: var(--color-text-3); +} + +.keywords-modal .keyword-trend { + display: flex; + align-items: center; + font-size: 16px; +} + +.keywords-modal .keyword-trend.up { + color: var(--color-success-6); +} + +.keywords-modal .keyword-trend.down { + color: var(--color-danger-6); +} + +.keywords-modal .keyword-trend.stable { + color: var(--color-text-3); +} + \ No newline at end of file diff --git a/dashboard/src/views/Live.vue b/dashboard/src/views/Live.vue index 6d3af0d..6f5f760 100644 --- a/dashboard/src/views/Live.vue +++ b/dashboard/src/views/Live.vue @@ -1,16 +1,1034 @@ - diff --git a/dashboard/src/views/LocalBookReader.vue b/dashboard/src/views/LocalBookReader.vue new file mode 100644 index 0000000..5640319 --- /dev/null +++ b/dashboard/src/views/LocalBookReader.vue @@ -0,0 +1,808 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/Parser.vue b/dashboard/src/views/Parser.vue new file mode 100644 index 0000000..eee2990 --- /dev/null +++ b/dashboard/src/views/Parser.vue @@ -0,0 +1,1108 @@ + + + + + diff --git a/dashboard/src/views/SearchAggregation.vue b/dashboard/src/views/SearchAggregation.vue new file mode 100644 index 0000000..2d4c4f8 --- /dev/null +++ b/dashboard/src/views/SearchAggregation.vue @@ -0,0 +1,1731 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/Settings.vue b/dashboard/src/views/Settings.vue index dad24ae..fe2367a 100644 --- a/dashboard/src/views/Settings.vue +++ b/dashboard/src/views/Settings.vue @@ -1,16 +1,2310 @@ - diff --git a/dashboard/src/views/Video.vue b/dashboard/src/views/Video.vue index d9c5fdc..92f6616 100644 --- a/dashboard/src/views/Video.vue +++ b/dashboard/src/views/Video.vue @@ -1,213 +1,1171 @@ - -
- {{ currentDateTime }} -
- + +const onSearch = async (value) => { + if (!value || !value.trim()) { + // 如果搜索内容为空,退出搜索模式 + searchState.isSearching = false; + searchState.searchKeyword = ""; + searchState.searchResults = []; + return; + } - diff --git a/dashboard/src/views/VideoDetail.vue b/dashboard/src/views/VideoDetail.vue new file mode 100644 index 0000000..9778755 --- /dev/null +++ b/dashboard/src/views/VideoDetail.vue @@ -0,0 +1,3188 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/views/VideoTest.vue b/dashboard/src/views/VideoTest.vue new file mode 100644 index 0000000..3a24d2b --- /dev/null +++ b/dashboard/src/views/VideoTest.vue @@ -0,0 +1,952 @@ + + + + + \ No newline at end of file diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js new file mode 100644 index 0000000..2f39beb --- /dev/null +++ b/dashboard/tailwind.config.js @@ -0,0 +1,52 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + }, + glass: { + white: 'rgba(255, 255, 255, 0.1)', + dark: 'rgba(0, 0, 0, 0.1)', + } + }, + backdropBlur: { + xs: '2px', + }, + animation: { + 'fade-in': 'fadeIn 0.3s ease-out', + 'slide-up': 'slideUp 0.3s ease-out', + 'scale-in': 'scaleIn 0.2s ease-out', + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }, + slideUp: { + '0%': { transform: 'translateY(20px)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' }, + }, + scaleIn: { + '0%': { transform: 'scale(0.95)', opacity: '0' }, + '100%': { transform: 'scale(1)', opacity: '1' }, + }, + }, + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/dashboard/temp-server/package.json b/dashboard/temp-server/package.json new file mode 100644 index 0000000..d5c4b81 --- /dev/null +++ b/dashboard/temp-server/package.json @@ -0,0 +1,28 @@ +{ + "name": "drplayer-server", + "version": "1.0.0", + "description": "DrPlayer Production Server - Minimal build for PKG", + "main": "./production-server.cjs", + "bin": "./production-server.cjs", + "type": "commonjs", + "scripts": { + "start": "node production-server.cjs", + "pkg:win": "pkg . --target node18-win-x64 --output dist-binary/drplayer-server-win.exe", + "pkg:win:optimized": "pkg . --target node18-win-x64 --output dist-binary/drplayer-server-win.exe --compress Brotli --public-packages \"*\" --public --options \"max-old-space-size=512\"" + }, + "dependencies": { + "fastify": "^4.29.1", + "@fastify/static": "^6.12.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "pkg": { + "assets": [ + "./apps/**/*" + ], + "scripts": [ + "./production-server.cjs" + ] + } +} \ No newline at end of file diff --git a/dashboard/temp-server/production-server.cjs b/dashboard/temp-server/production-server.cjs new file mode 100644 index 0000000..bf71518 --- /dev/null +++ b/dashboard/temp-server/production-server.cjs @@ -0,0 +1,386 @@ +#!/usr/bin/env node +/** + * 生产服务器 - DrPlayer Dashboard (CommonJS版本,用于PKG打包) + */ + +const Fastify = require('fastify'); +const fastifyStatic = require('@fastify/static'); +const path = require('path'); +const fs = require('fs/promises'); +const { execSync } = require('child_process'); +const net = require('net'); + +// 内联 SPA 路由功能 (避免ES6模块依赖) +async function addSPARoutes(fastify, options) { + const spaApps = options.spaApps || ['drplayer']; + + for (const appName of spaApps) { + fastify.get(`/apps/${appName}`, async (request, reply) => { + return reply.redirect(301, `/apps/${appName}/`); + }); + + fastify.get(`/apps/${appName}/`, async (request, reply) => { + const indexPath = path.join(options.appsDir, appName, 'index.html'); + + try { + console.log(`🔍 [SPA] 尝试读取index.html: ${indexPath}`); + const indexContent = await fs.readFile(indexPath, 'utf8'); + console.log(`✅ [SPA] 成功读取index.html,长度: ${indexContent.length}`); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); + } catch (error) { + console.error(`❌ [SPA] 读取index.html失败: ${error.message}`); + return reply.code(404).send({ error: `${appName} application not found` }); + } + }); + } + + fastify.setNotFoundHandler(async (request, reply) => { + const url = request.url; + + for (const appName of spaApps) { + const appPrefix = `/apps/${appName}/`; + + if (url.startsWith(appPrefix)) { + const urlPath = url.replace(appPrefix, ''); + const hasExtension = /\.[a-zA-Z0-9]+(\?.*)?$/.test(urlPath); + + if (!hasExtension) { + const indexPath = path.join(options.appsDir, appName, 'index.html'); + + try { + const indexContent = await fs.readFile(indexPath, 'utf8'); + return reply + .type('text/html') + .header('Cache-Control', 'no-cache, no-store, must-revalidate') + .send(indexContent); + } catch (error) { + return reply.code(404).send({ error: `${appName} application not found` }); + } + } + } + } + + return reply.code(404).send({ error: 'Not Found' }); + }); +} + +// PKG 环境下的路径处理 +const isPkg = typeof process.pkg !== 'undefined'; + +// 在CommonJS中,__filename和__dirname是全局可用的 +// 在PKG环境中需要重新定义 +let currentFilename, currentDirname; + +if (isPkg) { + currentFilename = process.execPath; + currentDirname = path.dirname(process.execPath); +} else { + currentFilename = __filename; + currentDirname = __dirname; +} + +// 检查端口是否可用 +function checkPortAvailable(port) { + return new Promise((resolve) => { + const server = net.createServer(); + + // 设置超时 + const timeout = setTimeout(() => { + server.close(); + resolve(false); + }, 1000); + + server.listen(port, '0.0.0.0', () => { + clearTimeout(timeout); + server.close(() => { + resolve(true); + }); + }); + + server.on('error', (err) => { + clearTimeout(timeout); + resolve(false); + }); + }); +} + +// 查找可用端口 +async function findAvailablePort(startPort = 9978, maxAttempts = 100) { + for (let i = 0; i < maxAttempts; i++) { + const port = startPort + i; + const isAvailable = await checkPortAvailable(port); + if (isAvailable) { + return port; + } + console.log(`端口 ${port} 已被占用,尝试下一个端口...`); + } + throw new Error(`无法找到可用端口,已尝试 ${startPort} 到 ${startPort + maxAttempts - 1}`); +} + +// PKG 环境下的工作目录处理 +let workDir; +if (isPkg) { + // 在PKG环境中,静态文件被打包在二进制文件内部,使用__dirname访问 + workDir = __dirname; + console.log('PKG 环境检测到,工作目录:', process.cwd()); + console.log('可执行文件路径:', process.execPath); + console.log('📦 PKG环境中使用内部资源路径:', workDir); +} else { + workDir = currentDirname; +} + +// 验证静态文件是否存在 +async function validateStaticFiles() { + if (isPkg) { + // PKG环境中,静态文件被打包在二进制文件内部,直接使用__dirname + const appsDir = path.join(__dirname, 'apps'); + const drplayerDir = path.join(appsDir, 'drplayer'); + + try { + // 在PKG环境中,使用readdir来验证目录存在性而不是access + const appsContents = await fs.readdir(appsDir); + + if (appsContents.includes('drplayer')) { + // 进一步验证drplayer目录内容 + const drplayerContents = await fs.readdir(drplayerDir); + + // 检查是否包含必要的文件(如index.html) + if (drplayerContents.includes('index.html')) { + console.log('✅ 静态文件目录验证成功:', drplayerDir); + return true; + } else { + console.error('❌ drplayer目录中缺少index.html文件'); + return false; + } + } else { + console.error('❌ apps目录中不包含drplayer子目录'); + return false; + } + } catch (error) { + console.error('❌ 无法读取PKG中的静态文件目录:', error.message); + return false; + } + } else { + // 非PKG环境 + const appsDir = path.join(workDir, 'apps'); + const drplayerDir = path.join(appsDir, 'drplayer'); + + try { + await fs.access(drplayerDir); + console.log('✅ 静态文件目录验证成功:', drplayerDir); + return true; + } catch (error) { + console.error('❌ 静态文件目录不存在:', drplayerDir); + console.error('请确保构建过程正确将静态文件复制到了正确位置'); + return false; + } + } +} + +// 主函数 +async function main() { + const fastify = Fastify({ + logger: false + }); + + let PORT = 9978; + + // PKG环境中的静态文件路径处理 + let appsDir; + if (isPkg) { + // PKG环境中,静态文件被打包在二进制文件内部,使用__dirname + appsDir = path.join(__dirname, 'apps'); + console.log('📦 PKG环境中使用内部资源路径:', appsDir); + } else { + // 开发环境中使用工作目录 + appsDir = path.join(workDir, 'apps'); + console.log('🔧 开发环境中使用工作目录:', appsDir); + } + + const options = { + appsDir: appsDir, + port: PORT + }; + + // 注册静态文件服务 + await fastify.register(fastifyStatic, { + root: options.appsDir, + prefix: '/apps/', + decorateReply: false, + }); + + // 注册SPA路由支持 + await fastify.register(addSPARoutes, { + appsDir: options.appsDir, + spaApps: ['drplayer'] + }); + + // 根路径 - 显示应用列表 + fastify.get('/', async (request, reply) => { + let version = '1.0.0'; + try { + const packageJsonPath = path.join(currentDirname, 'package.json'); + const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8'); + const packageJson = JSON.parse(packageJsonContent); + version = packageJson.version || '1.0.0'; + } catch (error) { + console.warn('无法读取package.json版本信息:', error.message); + } + + const html = ` + + + + + + DrPlayer Dashboard + + + +
+

🎬 DrPlayer Dashboard

+ +
+ ✅ 服务器运行正常 - 端口: ${PORT} +
+ + + + +
+ +`; + + return reply.type('text/html').send(html); + }); + + // 健康检查 + fastify.get('/health', async (request, reply) => { + return {status: 'ok', timestamp: new Date().toISOString()}; + }); + + // 启动服务器 + const start = async () => { + try { + // 验证静态文件是否存在 + const staticFilesValid = await validateStaticFiles(); + if (!staticFilesValid && isPkg) { + console.error('❌ PKG环境中静态文件验证失败,服务器无法正常运行'); + process.exit(1); + } + + console.log(`🔍 正在查找可用端口,起始端口: ${PORT}`); + const availablePort = await findAvailablePort(PORT); + PORT = availablePort; + options.port = PORT; + + console.log(`✅ 找到可用端口: ${PORT}`); + + await fastify.listen({ port: PORT, host: '0.0.0.0' }); + console.log(`🚀 生产服务器启动成功!`); + console.log(`📱 访问地址: http://localhost:${PORT}/apps/drplayer/`); + console.log(`🔍 健康检查: http://localhost:${PORT}/health`); + console.log(`📦 运行环境: ${isPkg ? 'PKG二进制' : '开发环境'}`); + + if (isPkg) { + console.log(`📁 工作目录: ${workDir}`); + console.log(`📂 应用目录: ${options.appsDir}`); + } + + } catch (err) { + console.error('❌ 服务器启动失败:', err.message); + fastify.log.error(err); + process.exit(1); + } + }; + + // 优雅关闭 + process.on('SIGINT', async () => { + console.log('\n🛑 正在关闭服务器...'); + await fastify.close(); + process.exit(0); + }); + + await start(); +} + +// 启动应用 +main().catch(console.error); \ No newline at end of file diff --git a/dashboard/tests/action-test.spec.js b/dashboard/tests/action-test.spec.js new file mode 100644 index 0000000..294a5f8 --- /dev/null +++ b/dashboard/tests/action-test.spec.js @@ -0,0 +1,221 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Action组件自动化测试', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/action-test'); + await page.waitForLoadState('networkidle'); + }); + + test('页面加载测试', async ({ page }) => { + // 检查页面标题 + await expect(page).toHaveTitle(/DrPlayer/); + + // 检查是否有测试按钮 + const inputButton = page.locator('button').filter({ hasText: '输入框测试' }).first(); + await expect(inputButton).toBeVisible(); + + console.log('Action Test Page loaded successfully'); + }); + + test('基础Action功能测试 - 输入框', async ({ page }) => { + console.log('开始测试输入框Action...'); + + // 点击输入框测试按钮 + const inputButton = page.locator('button').filter({ hasText: '输入框测试' }).first(); + await inputButton.click(); + + // 等待Action弹窗出现 + await page.waitForTimeout(1000); + + // 检查是否有Action弹窗 + const actionModal = page.locator('.action-modal, .modal, [role="dialog"]'); + const isModalVisible = await actionModal.isVisible().catch(() => false); + + if (!isModalVisible) { + console.log('❌ 输入框Action弹窗未出现'); + // 检查控制台错误 + const logs = await page.evaluate(() => { + return window.console.logs || []; + }); + console.log('控制台日志:', logs); + throw new Error('输入框Action弹窗未出现'); + } else { + console.log('✅ 输入框Action弹窗正常显示'); + + // 如果弹窗存在,测试交互 + const input = page.locator('input[type="text"], input:not([type])').first(); + if (await input.isVisible()) { + await input.fill('测试输入'); + console.log('✅ 输入框交互正常'); + } + + // 关闭弹窗 + const closeBtn = page.locator('button').filter({ hasText: /取消|关闭|Cancel/ }).first(); + if (await closeBtn.isVisible()) { + await closeBtn.click(); + } + } + }); + + test('基础Action功能测试 - 多输入框', async ({ page }) => { + console.log('开始测试多输入框Action...'); + + const multiInputButton = page.locator('button').filter({ hasText: '多输入框测试' }).first(); + await multiInputButton.click(); + await page.waitForTimeout(1000); + + const actionModal = page.locator('.action-modal, .modal, [role="dialog"]'); + const isModalVisible = await actionModal.isVisible().catch(() => false); + + if (!isModalVisible) { + console.log('❌ 多输入框Action弹窗未出现'); + throw new Error('多输入框Action弹窗未出现'); + } else { + console.log('✅ 多输入框Action弹窗正常显示'); + + // 关闭弹窗 + const closeBtn = page.locator('button').filter({ hasText: /取消|关闭|Cancel/ }).first(); + if (await closeBtn.isVisible()) { + await closeBtn.click(); + } + } + }); + + test('基础Action功能测试 - 菜单选择', async ({ page }) => { + console.log('开始测试菜单选择Action...'); + + const menuButton = page.locator('button').filter({ hasText: '菜单测试' }).first(); + await menuButton.click(); + await page.waitForTimeout(1000); + + const actionModal = page.locator('.action-modal, .modal, [role="dialog"]'); + const isModalVisible = await actionModal.isVisible().catch(() => false); + + if (!isModalVisible) { + console.log('❌ 菜单Action弹窗未出现'); + throw new Error('菜单Action弹窗未出现'); + } else { + console.log('✅ 菜单Action弹窗正常显示'); + + // 关闭弹窗 + const closeBtn = page.locator('button').filter({ hasText: /取消|关闭|Cancel/ }).first(); + if (await closeBtn.isVisible()) { + await closeBtn.click(); + } + } + }); + + test('便捷方法测试 - Alert', async ({ page }) => { + console.log('开始测试Alert便捷方法...'); + + const alertButton = page.locator('button').filter({ hasText: 'Alert' }).first(); + await alertButton.click(); + await page.waitForTimeout(1000); + + const actionModal = page.locator('.action-modal, .modal, [role="dialog"]'); + const isModalVisible = await actionModal.isVisible().catch(() => false); + + if (!isModalVisible) { + console.log('❌ Alert弹窗未出现'); + throw new Error('Alert弹窗未出现'); + } else { + console.log('✅ Alert弹窗正常显示'); + + // 关闭弹窗 + const okBtn = page.locator('button').filter({ hasText: /确定|OK/ }).first(); + if (await okBtn.isVisible()) { + await okBtn.click(); + } + } + }); + + test('便捷方法测试 - Confirm', async ({ page }) => { + console.log('开始测试Confirm便捷方法...'); + + const confirmButton = page.locator('button').filter({ hasText: 'Confirm' }).first(); + await confirmButton.click(); + await page.waitForTimeout(1000); + + const actionModal = page.locator('.action-modal, .modal, [role="dialog"]'); + const isModalVisible = await actionModal.isVisible().catch(() => false); + + if (!isModalVisible) { + console.log('❌ Confirm弹窗未出现'); + throw new Error('Confirm弹窗未出现'); + } else { + console.log('✅ Confirm弹窗正常显示'); + + // 关闭弹窗 + const cancelBtn = page.locator('button').filter({ hasText: /取消|Cancel/ }).first(); + if (await cancelBtn.isVisible()) { + await cancelBtn.click(); + } + } + }); + + test('控制台错误检测', async ({ page }) => { + console.log('开始检测控制台错误...'); + + const errors = []; + page.on('console', msg => { + if (msg.type() === 'error') { + errors.push(msg.text()); + } + }); + + // 测试几个主要按钮 + const buttons = [ + '输入框测试', + 'Alert', + 'Confirm' + ]; + + for (const buttonText of buttons) { + const button = page.locator('button').filter({ hasText: buttonText }).first(); + if (await button.isVisible()) { + await button.click(); + await page.waitForTimeout(500); + + // 尝试关闭弹窗 + const closeBtn = page.locator('button').filter({ hasText: /取消|关闭|确定|Cancel|OK/ }).first(); + if (await closeBtn.isVisible()) { + await closeBtn.click(); + } + await page.waitForTimeout(500); + } + } + + if (errors.length > 0) { + console.log('❌ 发现控制台错误:', errors); + console.log('需要修复的错误数量:', errors.length); + } else { + console.log('✅ 未发现控制台错误'); + } + }); + + test('Action状态管理测试', async ({ page }) => { + console.log('开始测试Action状态管理...'); + + // 检查ActionStateManager是否正确初始化 + const stateManagerExists = await page.evaluate(() => { + return window.ActionStateManager !== undefined; + }); + + if (!stateManagerExists) { + console.log('❌ ActionStateManager未正确初始化'); + } else { + console.log('✅ ActionStateManager正确初始化'); + } + + // 检查Actions对象是否存在 + const actionsExists = await page.evaluate(() => { + return window.Actions !== undefined; + }); + + if (!actionsExists) { + console.log('❌ Actions对象未正确导出'); + } else { + console.log('✅ Actions对象正确导出'); + } + }); +}); \ No newline at end of file diff --git a/dashboard/vercel.json b/dashboard/vercel.json new file mode 100644 index 0000000..1ee5562 --- /dev/null +++ b/dashboard/vercel.json @@ -0,0 +1,7 @@ +{ + "buildCommand": "pnpm build", + "outputDirectory": "dist", + "installCommand": "pnpm install --frozen-lockfile", + "framework": "vite", + "rewrites": [{ "source": "/:path*", "destination": "/index.html" }] +} \ No newline at end of file diff --git a/dashboard/vite.config.js b/dashboard/vite.config.js index 581f299..12a968a 100644 --- a/dashboard/vite.config.js +++ b/dashboard/vite.config.js @@ -1,22 +1,58 @@ -import {defineConfig} from 'vite' +import {defineConfig, loadEnv} from 'vite' import vue from '@vitejs/plugin-vue' -import Components from 'unplugin-vue-components/vite' -import {PrimeVueResolver} from '@primevue/auto-import-resolver' -import path from 'path'; +import path from 'path' +import { readFileSync } from 'fs' + +// 读取 package.json 中的版本号 +const packageJson = JSON.parse(readFileSync('./package.json', 'utf-8')) +const version = packageJson.version // https://vite.dev/config/ -export default defineConfig({ +export default defineConfig(({ command, mode }) => { + // 加载环境变量 + const env = loadEnv(mode, process.cwd(), '') + + return { + // 基础路径配置,支持子目录部署 + // 开发环境使用 '/',生产环境可以通过环境变量设置 + base: mode.includes('production') ? (env.VITE_BASE_PATH || './') : '/', + + // 定义全局变量 + define: { + __APP_VERSION__: JSON.stringify(version) + }, + plugins: [ vue(), - Components({ - resolvers: [ - PrimeVueResolver() - ] - }) ], + + // 构建配置 + build: { + // 输出目录 + outDir: 'dist', + // 静态资源目录 + assetsDir: 'assets', + // 生成相对路径的资源引用 + rollupOptions: { + output: { + // 静态资源文件名格式 + assetFileNames: 'assets/[name]-[hash][extname]', + chunkFileNames: 'assets/[name]-[hash].js', + entryFileNames: 'assets/[name]-[hash].js' + } + } + }, + + optimizeDeps: { + include: [ + '@arco-design/web-vue/es/icon' + ] + }, + resolve: { alias: { '@': path.resolve(__dirname, 'src'), // 配置别名 }, }, -}) + } +}) \ No newline at end of file diff --git a/dashboard/yarn.lock b/dashboard/yarn.lock deleted file mode 100644 index 419f223..0000000 --- a/dashboard/yarn.lock +++ /dev/null @@ -1,841 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@antfu/utils@^0.7.10": - version "0.7.10" - resolved "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz" - integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" - integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/parser@^7.25.3": - version "7.26.2" - resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz" - integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== - dependencies: - "@babel/types" "^7.26.0" - -"@babel/types@^7.26.0": - version "7.26.0" - resolved "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz" - integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@esbuild/aix-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" - integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== - -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - -"@esbuild/android-arm@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" - integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== - -"@esbuild/android-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" - integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== - -"@esbuild/darwin-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" - integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== - -"@esbuild/darwin-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" - integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== - -"@esbuild/freebsd-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" - integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== - -"@esbuild/freebsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" - integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== - -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - -"@esbuild/linux-arm@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" - integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== - -"@esbuild/linux-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" - integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== - -"@esbuild/linux-loong64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" - integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== - -"@esbuild/linux-mips64el@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" - integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== - -"@esbuild/linux-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" - integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== - -"@esbuild/linux-riscv64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" - integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== - -"@esbuild/linux-s390x@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" - integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== - -"@esbuild/linux-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" - integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== - -"@esbuild/netbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" - integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== - -"@esbuild/openbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" - integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== - -"@esbuild/sunos-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" - integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== - -"@esbuild/win32-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" - integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== - -"@esbuild/win32-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" - integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== - -"@esbuild/win32-x64@0.21.5": - version "0.21.5" - resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" - integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== - -"@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@primeuix/styled@^0.3.0": - version "0.3.0" - resolved "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.3.0.tgz" - integrity sha512-XsLbmyM1u50A0EDATIHyqm5O/zOCSyNKPk4pNN8HFvEPehbsjf4tkXcRZAyaVvntSCLpV4XGAj7v5EDCQkBRlg== - dependencies: - "@primeuix/utils" "^0.3.0" - -"@primeuix/utils@^0.3.0": - version "0.3.0" - resolved "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.3.0.tgz" - integrity sha512-d6ymWez1n+iqwzAVhyOTmrOHl5qnSX2oGlTy97qGuA15gLai+MQaxONHFNdDia8Q7o396v7KK9IvhAx9VET/+A== - -"@primevue/auto-import-resolver@^4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@primevue/auto-import-resolver/-/auto-import-resolver-4.2.1.tgz#da3ca2a15dbfd3b21385bfa6faba76d5a9c5af08" - integrity sha512-NR2jTme+R/p9oapvysh4y8vFR6/w2ymK4lOzx+pZHHb+QGh8lJvkvJ5NWhwOhO9YD4RGOlQGKA4kcY2Z1itafA== - dependencies: - "@primevue/metadata" "4.2.1" - -"@primevue/core@4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@primevue/core/-/core-4.2.1.tgz" - integrity sha512-L81TZSZU8zRznIi2g6IWwlZ5wraaE8DrNUJyxieCRCTpbSF3rSlYmhDEuzal8PfE0RuvXpRsxqedTHxz5cdqPg== - dependencies: - "@primeuix/styled" "^0.3.0" - "@primeuix/utils" "^0.3.0" - -"@primevue/icons@4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@primevue/icons/-/icons-4.2.1.tgz" - integrity sha512-TOhxgkcmgBqmlHlf2x+gs4874iHopkow0gRAC5FztZTgTZQrqy8hPIA9b4O1lW7P6GOjGuVIwSH8y2lw6Q8koA== - dependencies: - "@primeuix/utils" "^0.3.0" - "@primevue/core" "4.2.1" - -"@primevue/metadata@4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@primevue/metadata/-/metadata-4.2.1.tgz" - integrity sha512-XX29c2FtbXo0EX8GoYYT9os0FMxAZBPqq6VTAhbHrIUWzKnR8SVrxWoyy6G0wbzP3qXD4X3T7wUhjvQYHtTzLg== - -"@primevue/themes@^4.2.1": - version "4.2.1" - resolved "https://registry.npmmirror.com/@primevue/themes/-/themes-4.2.1.tgz" - integrity sha512-byp4YejyVdrOpRRbq5vBtaDBFHUq7Wc0aGWwII1fliYbwQ+WXn/hCAYhaXwRrwweHpTiobiWWsS+PRLWJ7fBRw== - dependencies: - "@primeuix/styled" "^0.3.0" - -"@rollup/pluginutils@^5.1.0": - version "5.1.3" - resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz" - integrity sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A== - dependencies: - "@types/estree" "^1.0.0" - estree-walker "^2.0.2" - picomatch "^4.0.2" - -"@rollup/rollup-android-arm-eabi@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz#c460b54c50d42f27f8254c435a4f3b3e01910bc8" - integrity sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw== - -"@rollup/rollup-android-arm64@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz#96e01f3a04675d8d5973ab8d3fd6bc3be21fa5e1" - integrity sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA== - -"@rollup/rollup-darwin-arm64@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz#9b2ec23b17b47cbb2f771b81f86ede3ac6730bce" - integrity sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ== - -"@rollup/rollup-darwin-x64@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz#f30e4ee6929e048190cf10e0daa8e8ae035b6e46" - integrity sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg== - -"@rollup/rollup-freebsd-arm64@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz#c54b2373ec5bcf71f08c4519c7ae80a0b6c8e03b" - integrity sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw== - -"@rollup/rollup-freebsd-x64@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz#3bc53aa29d5a34c28ba8e00def76aa612368458e" - integrity sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g== - -"@rollup/rollup-linux-arm-gnueabihf@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz#c85aedd1710c9e267ee86b6d1ce355ecf7d9e8d9" - integrity sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA== - -"@rollup/rollup-linux-arm-musleabihf@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz#e77313408bf13995aecde281aec0cceb08747e42" - integrity sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw== - -"@rollup/rollup-linux-arm64-gnu@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz#633f632397b3662108cfaa1abca2a80b85f51102" - integrity sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg== - -"@rollup/rollup-linux-arm64-musl@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz#63edd72b29c4cced93e16113a68e1be9fef88907" - integrity sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA== - -"@rollup/rollup-linux-powerpc64le-gnu@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz#a9418a4173df80848c0d47df0426a0bf183c4e75" - integrity sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA== - -"@rollup/rollup-linux-riscv64-gnu@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz#bc9c195db036a27e5e3339b02f51526b4ce1e988" - integrity sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw== - -"@rollup/rollup-linux-s390x-gnu@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz#1651fdf8144ae89326c01da5d52c60be63e71a82" - integrity sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ== - -"@rollup/rollup-linux-x64-gnu@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz#e473de5e4acb95fcf930a35cbb7d3e8080e57a6f" - integrity sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA== - -"@rollup/rollup-linux-x64-musl@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz#0af12dd2578c29af4037f0c834b4321429dd5b01" - integrity sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q== - -"@rollup/rollup-win32-arm64-msvc@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz#e48e78cdd45313b977c1390f4bfde7ab79be8871" - integrity sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA== - -"@rollup/rollup-win32-ia32-msvc@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz#a3fc8536d243fe161c796acb93eba43c250f311c" - integrity sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg== - -"@rollup/rollup-win32-x64-msvc@4.24.4": - version "4.24.4" - resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz" - integrity sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg== - -"@types/estree@1.0.6", "@types/estree@^1.0.0": - version "1.0.6" - resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - -"@vitejs/plugin-vue@^5.1.4": - version "5.1.4" - resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz" - integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== - -"@vue/compiler-core@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.12.tgz" - integrity sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw== - dependencies: - "@babel/parser" "^7.25.3" - "@vue/shared" "3.5.12" - entities "^4.5.0" - estree-walker "^2.0.2" - source-map-js "^1.2.0" - -"@vue/compiler-dom@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz" - integrity sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg== - dependencies: - "@vue/compiler-core" "3.5.12" - "@vue/shared" "3.5.12" - -"@vue/compiler-sfc@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz" - integrity sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw== - dependencies: - "@babel/parser" "^7.25.3" - "@vue/compiler-core" "3.5.12" - "@vue/compiler-dom" "3.5.12" - "@vue/compiler-ssr" "3.5.12" - "@vue/shared" "3.5.12" - estree-walker "^2.0.2" - magic-string "^0.30.11" - postcss "^8.4.47" - source-map-js "^1.2.0" - -"@vue/compiler-ssr@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz" - integrity sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA== - dependencies: - "@vue/compiler-dom" "3.5.12" - "@vue/shared" "3.5.12" - -"@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4": - version "6.6.4" - resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" - integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== - -"@vue/reactivity@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.12.tgz" - integrity sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg== - dependencies: - "@vue/shared" "3.5.12" - -"@vue/runtime-core@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.12.tgz" - integrity sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw== - dependencies: - "@vue/reactivity" "3.5.12" - "@vue/shared" "3.5.12" - -"@vue/runtime-dom@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz" - integrity sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA== - dependencies: - "@vue/reactivity" "3.5.12" - "@vue/runtime-core" "3.5.12" - "@vue/shared" "3.5.12" - csstype "^3.1.3" - -"@vue/server-renderer@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.12.tgz" - integrity sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg== - dependencies: - "@vue/compiler-ssr" "3.5.12" - "@vue/shared" "3.5.12" - -"@vue/shared@3.5.12": - version "3.5.12" - resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.12.tgz" - integrity sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg== - -acorn@^8.12.1, acorn@^8.14.0: - version "8.14.0" - resolved "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -chokidar@^3.6.0: - version "3.6.0" - resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -confbox@^0.1.8: - version "0.1.8" - resolved "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz" - integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== - -csstype@^3.1.3: - version "3.1.3" - resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -debug@^4.3.6: - version "4.3.7" - resolved "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -entities@^4.5.0: - version "4.5.0" - resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -esbuild@^0.21.3: - version "0.21.5" - resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz" - integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== - optionalDependencies: - "@esbuild/aix-ppc64" "0.21.5" - "@esbuild/android-arm" "0.21.5" - "@esbuild/android-arm64" "0.21.5" - "@esbuild/android-x64" "0.21.5" - "@esbuild/darwin-arm64" "0.21.5" - "@esbuild/darwin-x64" "0.21.5" - "@esbuild/freebsd-arm64" "0.21.5" - "@esbuild/freebsd-x64" "0.21.5" - "@esbuild/linux-arm" "0.21.5" - "@esbuild/linux-arm64" "0.21.5" - "@esbuild/linux-ia32" "0.21.5" - "@esbuild/linux-loong64" "0.21.5" - "@esbuild/linux-mips64el" "0.21.5" - "@esbuild/linux-ppc64" "0.21.5" - "@esbuild/linux-riscv64" "0.21.5" - "@esbuild/linux-s390x" "0.21.5" - "@esbuild/linux-x64" "0.21.5" - "@esbuild/netbsd-x64" "0.21.5" - "@esbuild/openbsd-x64" "0.21.5" - "@esbuild/sunos-x64" "0.21.5" - "@esbuild/win32-arm64" "0.21.5" - "@esbuild/win32-ia32" "0.21.5" - "@esbuild/win32-x64" "0.21.5" - -estree-walker@^2.0.2: - version "2.0.2" - resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -fast-glob@^3.3.2: - version "3.3.2" - resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -local-pkg@^0.5.0: - version "0.5.0" - resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.0.tgz" - integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== - dependencies: - mlly "^1.4.2" - pkg-types "^1.0.3" - -magic-string@^0.30.11: - version "0.30.12" - resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.12.tgz" - integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -minimatch@^9.0.5: - version "9.0.5" - resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -mlly@^1.4.2, mlly@^1.7.1, mlly@^1.7.2: - version "1.7.2" - resolved "https://registry.npmmirror.com/mlly/-/mlly-1.7.2.tgz" - integrity sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA== - dependencies: - acorn "^8.12.1" - pathe "^1.1.2" - pkg-types "^1.2.0" - ufo "^1.5.4" - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -pathe@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz" - integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== - -picocolors@^1.1.0: - version "1.1.1" - resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -pinia@^2.2.6: - version "2.2.6" - resolved "https://registry.npmmirror.com/pinia/-/pinia-2.2.6.tgz#ff93f35b8c02033eaedc8c92ad5f10f215d6c804" - integrity sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA== - dependencies: - "@vue/devtools-api" "^6.6.3" - vue-demi "^0.14.10" - -pkg-types@^1.0.3, pkg-types@^1.2.0: - version "1.2.1" - resolved "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.2.1.tgz" - integrity sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw== - dependencies: - confbox "^0.1.8" - mlly "^1.7.2" - pathe "^1.1.2" - -postcss@^8.4.43, postcss@^8.4.47: - version "8.4.47" - resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.47.tgz" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - -primeflex@^3.3.1: - version "3.3.1" - resolved "https://registry.npmmirror.com/primeflex/-/primeflex-3.3.1.tgz#361dddf6eb5db50d733e4cddd4b6e376a3d7bd68" - integrity sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ== - -primeicons@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/primeicons/-/primeicons-7.0.0.tgz#6b25c3fdcb29bb745a3035bdc1ed5902f4a419cf" - integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw== - -primevue@^4.2.1: - version "4.2.1" - resolved "https://registry.npmmirror.com/primevue/-/primevue-4.2.1.tgz#31c206b60ea9fef4c3f069b49a92e8cce3ad0498" - integrity sha512-cU9ZQVq9fitsQEIrfGeIl7xELBn61JCMxWkzcS9dkr165g29AvUrUNS9ufs1t2NoMJzE8VllwzweF/tSFAr2cw== - dependencies: - "@primeuix/styled" "^0.3.0" - "@primeuix/utils" "^0.3.0" - "@primevue/core" "4.2.1" - "@primevue/icons" "4.2.1" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rollup@^4.20.0: - version "4.24.4" - resolved "https://registry.npmmirror.com/rollup/-/rollup-4.24.4.tgz" - integrity sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.24.4" - "@rollup/rollup-android-arm64" "4.24.4" - "@rollup/rollup-darwin-arm64" "4.24.4" - "@rollup/rollup-darwin-x64" "4.24.4" - "@rollup/rollup-freebsd-arm64" "4.24.4" - "@rollup/rollup-freebsd-x64" "4.24.4" - "@rollup/rollup-linux-arm-gnueabihf" "4.24.4" - "@rollup/rollup-linux-arm-musleabihf" "4.24.4" - "@rollup/rollup-linux-arm64-gnu" "4.24.4" - "@rollup/rollup-linux-arm64-musl" "4.24.4" - "@rollup/rollup-linux-powerpc64le-gnu" "4.24.4" - "@rollup/rollup-linux-riscv64-gnu" "4.24.4" - "@rollup/rollup-linux-s390x-gnu" "4.24.4" - "@rollup/rollup-linux-x64-gnu" "4.24.4" - "@rollup/rollup-linux-x64-musl" "4.24.4" - "@rollup/rollup-win32-arm64-msvc" "4.24.4" - "@rollup/rollup-win32-ia32-msvc" "4.24.4" - "@rollup/rollup-win32-x64-msvc" "4.24.4" - fsevents "~2.3.2" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -source-map-js@^1.2.0, source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ufo@^1.5.4: - version "1.5.4" - resolved "https://registry.npmmirror.com/ufo/-/ufo-1.5.4.tgz" - integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== - -unplugin-vue-components@^0.27.4: - version "0.27.4" - resolved "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz#748468b2cb6a856235bdd512c33b629c33c3d23a" - integrity sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ== - dependencies: - "@antfu/utils" "^0.7.10" - "@rollup/pluginutils" "^5.1.0" - chokidar "^3.6.0" - debug "^4.3.6" - fast-glob "^3.3.2" - local-pkg "^0.5.0" - magic-string "^0.30.11" - minimatch "^9.0.5" - mlly "^1.7.1" - unplugin "^1.12.1" - -unplugin@^1.12.1: - version "1.15.0" - resolved "https://registry.npmmirror.com/unplugin/-/unplugin-1.15.0.tgz" - integrity sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA== - dependencies: - acorn "^8.14.0" - webpack-virtual-modules "^0.6.2" - -vite@^5.4.10: - version "5.4.10" - resolved "https://registry.npmmirror.com/vite/-/vite-5.4.10.tgz" - integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ== - dependencies: - esbuild "^0.21.3" - postcss "^8.4.43" - rollup "^4.20.0" - optionalDependencies: - fsevents "~2.3.3" - -vue-demi@^0.14.10: - version "0.14.10" - resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04" - integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== - -vue-router@^4.4.5: - version "4.4.5" - resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.5.tgz#bdf535e4cf32414ebdea6b4b403593efdb541388" - integrity sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q== - dependencies: - "@vue/devtools-api" "^6.6.4" - -vue@^3.5.12: - version "3.5.12" - resolved "https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz" - integrity sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg== - dependencies: - "@vue/compiler-dom" "3.5.12" - "@vue/compiler-sfc" "3.5.12" - "@vue/runtime-dom" "3.5.12" - "@vue/server-renderer" "3.5.12" - "@vue/shared" "3.5.12" - -webpack-virtual-modules@^0.6.2: - version "0.6.2" - resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz" - integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== diff --git a/img.png b/img.png deleted file mode 100644 index 0ba9d9a..0000000 Binary files a/img.png and /dev/null differ diff --git a/img_1.png b/img_1.png deleted file mode 100644 index d90126d..0000000 Binary files a/img_1.png and /dev/null differ diff --git a/img_2.png b/img_2.png deleted file mode 100644 index 1171e76..0000000 Binary files a/img_2.png and /dev/null differ diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 0000000..deb3ebe --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,196 @@ +# 代理服务器优化版本 + +## 🚀 优化内容 + +### 内存泄漏问题解决 + +1. **连接池管理** + - 实现了全局HTTP客户端单例,避免重复创建 + - 配置连接池限制:最大100个连接,保持20个活跃连接 + - 设置连接过期时间:30秒自动清理空闲连接 + +2. **资源自动释放** + - 自定义`ProxyStreamingResponse`确保流式响应正确关闭 + - 实现`stream_response_with_cleanup`自动清理响应资源 + - 应用生命周期管理,启动和关闭时正确初始化和清理资源 + +3. **内存监控系统** + - 实时监控内存使用情况(每30秒检查一次) + - 内存使用超过500MB时自动触发清理 + - 内存使用超过400MB时执行轻量级垃圾回收 + - 提供手动清理接口:`POST /admin/cleanup` + +4. **超时和错误处理** + - 连接超时:10秒 + - 读取超时:60秒 + - 写入超时:10秒 + - 连接池超时:5秒 + - 详细的错误分类和处理 + +## 📊 性能提升 + +- **内存使用优化**:解决了无限内存增长问题 +- **连接复用**:减少连接建立开销 +- **自动清理**:防止资源泄漏 +- **监控告警**:实时掌握服务器状态 + +## 🛠️ 安装和使用 + +### 1. 安装依赖 + +```bash +cd proxy +pip install -r requirements.txt +``` + +### 2. 启动服务器 + +#### 方式一:直接启动 +```bash +python proxy.py +``` + +#### 方式二:使用启动脚本(推荐) +```bash +python start_proxy.py --host 0.0.0.0 --port 8000 +``` + +#### 方式三:使用环境变量配置 +```bash +export PROXY_HOST=0.0.0.0 +export PROXY_PORT=8000 +export PROXY_MAX_MEMORY_USAGE=300 +python proxy.py +``` + +### 3. 健康检查 + +访问 `http://localhost:8000/health` 查看服务器状态和内存使用情况。 + +### 4. 手动清理内存 + +```bash +curl -X POST http://localhost:8000/admin/cleanup +``` + +## ⚙️ 配置选项 + +### 环境变量配置 + +| 变量名 | 默认值 | 说明 | +|--------|--------|------| +| `PROXY_HOST` | 0.0.0.0 | 绑定主机地址 | +| `PROXY_PORT` | 8000 | 绑定端口 | +| `PROXY_MAX_CONNECTIONS` | 100 | 最大连接数 | +| `PROXY_MAX_KEEPALIVE_CONNECTIONS` | 20 | 最大保持连接数 | +| `PROXY_KEEPALIVE_EXPIRY` | 30.0 | 连接保持时间(秒) | +| `PROXY_CONNECT_TIMEOUT` | 10.0 | 连接超时(秒) | +| `PROXY_READ_TIMEOUT` | 60.0 | 读取超时(秒) | +| `PROXY_WRITE_TIMEOUT` | 10.0 | 写入超时(秒) | +| `PROXY_MEMORY_CHECK_INTERVAL` | 30 | 内存检查间隔(秒) | +| `PROXY_MAX_MEMORY_USAGE` | 500 | 最大内存使用(MB) | +| `PROXY_CLEANUP_THRESHOLD` | 400 | 清理阈值(MB) | + +### 配置文件 + +创建 `proxy_config.json` 文件: + +```json +{ + "host": "0.0.0.0", + "port": 8000, + "max_connections": 100, + "max_keepalive_connections": 20, + "keepalive_expiry": 30.0, + "connect_timeout": 10.0, + "read_timeout": 60.0, + "write_timeout": 10.0, + "memory_check_interval": 30, + "max_memory_usage": 500, + "cleanup_threshold": 400 +} +``` + +## 📈 监控和日志 + +### 日志文件 + +服务器运行日志保存在 `proxy.log` 文件中,包含: +- 请求日志 +- 内存使用情况 +- 错误信息 +- 清理操作记录 + +### 监控接口 + +- `GET /health` - 健康检查和内存使用情况 +- `POST /admin/cleanup` - 手动触发内存清理 + +### 示例监控脚本 + +```bash +#!/bin/bash +# monitor.sh - 监控脚本示例 + +while true; do + response=$(curl -s http://localhost:8000/health) + memory=$(echo $response | jq -r '.memory_usage_mb') + + echo "$(date): 内存使用 ${memory}MB" + + if (( $(echo "$memory > 400" | bc -l) )); then + echo "内存使用过高,触发清理..." + curl -X POST http://localhost:8000/admin/cleanup + fi + + sleep 60 +done +``` + +## 🔧 故障排除 + +### 常见问题 + +1. **内存使用仍然很高** + - 检查 `PROXY_MAX_MEMORY_USAGE` 和 `PROXY_CLEANUP_THRESHOLD` 设置 + - 查看日志文件确认清理操作是否正常执行 + - 考虑降低 `PROXY_MAX_CONNECTIONS` 值 + +2. **连接超时** + - 调整 `PROXY_CONNECT_TIMEOUT` 和 `PROXY_READ_TIMEOUT` + - 检查目标服务器的响应时间 + +3. **服务器无响应** + - 检查端口是否被占用 + - 查看日志文件中的错误信息 + - 尝试重启服务器 + +### 性能调优建议 + +1. **根据服务器配置调整连接数**: + - 低配置服务器:`MAX_CONNECTIONS=50` + - 高配置服务器:`MAX_CONNECTIONS=200` + +2. **根据网络环境调整超时**: + - 内网环境:较短的超时时间 + - 外网环境:较长的超时时间 + +3. **根据内存大小调整阈值**: + - 小内存服务器:降低 `MAX_MEMORY_USAGE` + - 大内存服务器:可以适当提高阈值 + +## 📝 更新日志 + +### v2.0.0 (当前版本) +- ✅ 解决内存泄漏问题 +- ✅ 添加连接池管理 +- ✅ 实现内存监控系统 +- ✅ 优化资源释放机制 +- ✅ 添加健康检查接口 +- ✅ 完善错误处理 +- ✅ 添加配置管理 + +### v1.0.0 (原始版本) +- ❌ 存在内存泄漏问题 +- ❌ 缺乏资源管理 +- ❌ 无监控机制 \ No newline at end of file diff --git a/proxy/config.py b/proxy/config.py new file mode 100644 index 0000000..d0ca6f8 --- /dev/null +++ b/proxy/config.py @@ -0,0 +1,85 @@ +""" +代理服务器配置文件 +可以通过环境变量或配置文件覆盖默认设置 +""" + +import os +from typing import Optional + +class Config: + """配置类""" + + # 服务器配置 + HOST: str = os.getenv("PROXY_HOST", "0.0.0.0") + PORT: int = int(os.getenv("PROXY_PORT", "8000")) + WORKERS: int = int(os.getenv("PROXY_WORKERS", "1")) + + # 连接池配置 + MAX_CONNECTIONS: int = int(os.getenv("PROXY_MAX_CONNECTIONS", "100")) + MAX_KEEPALIVE_CONNECTIONS: int = int(os.getenv("PROXY_MAX_KEEPALIVE_CONNECTIONS", "20")) + KEEPALIVE_EXPIRY: float = float(os.getenv("PROXY_KEEPALIVE_EXPIRY", "30.0")) + + # 超时配置 + CONNECT_TIMEOUT: float = float(os.getenv("PROXY_CONNECT_TIMEOUT", "10.0")) + READ_TIMEOUT: float = float(os.getenv("PROXY_READ_TIMEOUT", "60.0")) + WRITE_TIMEOUT: float = float(os.getenv("PROXY_WRITE_TIMEOUT", "10.0")) + POOL_TIMEOUT: float = float(os.getenv("PROXY_POOL_TIMEOUT", "5.0")) + + # 内存监控配置 + MEMORY_CHECK_INTERVAL: int = int(os.getenv("PROXY_MEMORY_CHECK_INTERVAL", "30")) + MAX_MEMORY_USAGE: int = int(os.getenv("PROXY_MAX_MEMORY_USAGE", "500")) + CLEANUP_THRESHOLD: int = int(os.getenv("PROXY_CLEANUP_THRESHOLD", "400")) + + # 日志配置 + LOG_LEVEL: str = os.getenv("PROXY_LOG_LEVEL", "INFO") + LOG_FILE: Optional[str] = os.getenv("PROXY_LOG_FILE", "proxy.log") + + # 安全配置 + VERIFY_SSL: bool = os.getenv("PROXY_VERIFY_SSL", "false").lower() == "true" + ALLOWED_HOSTS: Optional[str] = os.getenv("PROXY_ALLOWED_HOSTS") # 逗号分隔的主机列表 + + # 性能配置 + CHUNK_SIZE: int = int(os.getenv("PROXY_CHUNK_SIZE", "8192")) + ENABLE_COMPRESSION: bool = os.getenv("PROXY_ENABLE_COMPRESSION", "true").lower() == "true" + + @classmethod + def get_allowed_hosts(cls) -> list: + """获取允许的主机列表""" + if cls.ALLOWED_HOSTS: + return [host.strip() for host in cls.ALLOWED_HOSTS.split(",")] + return ["*"] # 默认允许所有主机 + + @classmethod + def load_from_file(cls, config_file: str): + """从配置文件加载配置""" + try: + import json + with open(config_file, 'r', encoding='utf-8') as f: + config_data = json.load(f) + + for key, value in config_data.items(): + if hasattr(cls, key.upper()): + setattr(cls, key.upper(), value) + except FileNotFoundError: + pass # 配置文件不存在时使用默认值 + except Exception as e: + print(f"加载配置文件失败: {e}") + + @classmethod + def save_to_file(cls, config_file: str): + """保存配置到文件""" + import json + + config_data = {} + for attr in dir(cls): + if attr.isupper() and not attr.startswith('_'): + config_data[attr.lower()] = getattr(cls, attr) + + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(config_data, f, indent=2, ensure_ascii=False) + +# 默认配置实例 +config = Config() + +# 尝试加载配置文件 +config.load_from_file("proxy_config.json") \ No newline at end of file diff --git a/proxy/proxy.py b/proxy/proxy.py new file mode 100644 index 0000000..9e71081 --- /dev/null +++ b/proxy/proxy.py @@ -0,0 +1,337 @@ +import asyncio +import gc +import logging +import psutil +import time +from contextlib import asynccontextmanager +from typing import Optional + +from fastapi import FastAPI, Request, HTTPException, UploadFile +from fastapi.responses import StreamingResponse +from fastapi.middleware.cors import CORSMiddleware +import httpx + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# 全局配置 +class ProxyConfig: + # 连接池配置 + MAX_CONNECTIONS = 100 # 最大连接数 + MAX_KEEPALIVE_CONNECTIONS = 20 # 最大保持连接数 + KEEPALIVE_EXPIRY = 30.0 # 连接保持时间(秒) + + # 超时配置 + CONNECT_TIMEOUT = 10.0 # 连接超时 + READ_TIMEOUT = 60.0 # 读取超时 + WRITE_TIMEOUT = 10.0 # 写入超时 + POOL_TIMEOUT = 5.0 # 连接池超时 + + # 内存监控配置 + MEMORY_CHECK_INTERVAL = 30 # 内存检查间隔(秒) + MAX_MEMORY_USAGE = 500 # 最大内存使用(MB) + CLEANUP_THRESHOLD = 400 # 清理阈值(MB) + +# 全局变量 +http_client: Optional[httpx.AsyncClient] = None +memory_monitor_task: Optional[asyncio.Task] = None + +class MemoryMonitor: + """内存监控器""" + + @staticmethod + def get_memory_usage() -> float: + """获取当前进程内存使用量(MB)""" + process = psutil.Process() + return process.memory_info().rss / 1024 / 1024 + + @staticmethod + async def cleanup_resources(): + """清理资源""" + logger.info("开始清理资源...") + + # 强制垃圾回收 + collected = gc.collect() + logger.info(f"垃圾回收清理了 {collected} 个对象") + + # 如果内存使用仍然过高,重新创建HTTP客户端 + current_memory = MemoryMonitor.get_memory_usage() + if current_memory > ProxyConfig.CLEANUP_THRESHOLD: + await recreate_http_client() + logger.info("重新创建HTTP客户端以释放内存") + + @staticmethod + async def monitor_memory(): + """内存监控循环""" + while True: + try: + current_memory = MemoryMonitor.get_memory_usage() + logger.info(f"当前内存使用: {current_memory:.2f} MB") + + if current_memory > ProxyConfig.MAX_MEMORY_USAGE: + logger.warning(f"内存使用过高: {current_memory:.2f} MB,开始清理") + await MemoryMonitor.cleanup_resources() + elif current_memory > ProxyConfig.CLEANUP_THRESHOLD: + logger.info("内存使用接近阈值,执行轻量级清理") + gc.collect() + + await asyncio.sleep(ProxyConfig.MEMORY_CHECK_INTERVAL) + except Exception as e: + logger.error(f"内存监控出错: {e}") + await asyncio.sleep(ProxyConfig.MEMORY_CHECK_INTERVAL) + +async def create_http_client() -> httpx.AsyncClient: + """创建HTTP客户端""" + limits = httpx.Limits( + max_connections=ProxyConfig.MAX_CONNECTIONS, + max_keepalive_connections=ProxyConfig.MAX_KEEPALIVE_CONNECTIONS, + keepalive_expiry=ProxyConfig.KEEPALIVE_EXPIRY + ) + + timeout = httpx.Timeout( + connect=ProxyConfig.CONNECT_TIMEOUT, + read=ProxyConfig.READ_TIMEOUT, + write=ProxyConfig.WRITE_TIMEOUT, + pool=ProxyConfig.POOL_TIMEOUT + ) + + return httpx.AsyncClient( + limits=limits, + timeout=timeout, + follow_redirects=True, + verify=False # 忽略SSL验证,避免某些网站的证书问题 + ) + +async def recreate_http_client(): + """重新创建HTTP客户端""" + global http_client + + if http_client: + await http_client.aclose() + logger.info("已关闭旧的HTTP客户端") + + http_client = await create_http_client() + logger.info("已创建新的HTTP客户端") + +@asynccontextmanager +async def lifespan(app: FastAPI): + """应用生命周期管理""" + global http_client, memory_monitor_task + + # 启动时初始化 + logger.info("正在启动代理服务器...") + http_client = await create_http_client() + + # 启动内存监控 + memory_monitor_task = asyncio.create_task(MemoryMonitor.monitor_memory()) + logger.info("内存监控已启动") + + yield + + # 关闭时清理 + logger.info("正在关闭代理服务器...") + + if memory_monitor_task: + memory_monitor_task.cancel() + try: + await memory_monitor_task + except asyncio.CancelledError: + pass + + if http_client: + await http_client.aclose() + logger.info("HTTP客户端已关闭") + +# 创建FastAPI应用 +app = FastAPI(lifespan=lifespan) + +# 设置跨域支持 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +class ProxyStreamingResponse(StreamingResponse): + """自定义流式响应,确保资源正确释放""" + + def __init__(self, content, **kwargs): + super().__init__(content, **kwargs) + self._content_consumed = False + + async def __call__(self, scope, receive, send): + try: + await super().__call__(scope, receive, send) + finally: + # 确保内容被完全消费 + if hasattr(self, 'body_iterator') and not self._content_consumed: + try: + async for _ in self.body_iterator: + pass + except Exception: + pass + self._content_consumed = True + +async def stream_response_with_cleanup(response: httpx.Response): + """带清理功能的流式响应生成器""" + try: + async for chunk in response.aiter_bytes(chunk_size=8192): + yield chunk + except Exception as e: + logger.error(f"流式传输出错: {e}") + raise + finally: + # 确保响应被正确关闭 + if not response.is_closed: + await response.aclose() + +@app.api_route('/proxy/{full_url:path}', methods=['GET', 'POST', 'PUT', 'DELETE']) +async def proxy(request: Request, full_url: str): + """代理请求处理""" + global http_client + + if not http_client: + raise HTTPException(status_code=503, detail="HTTP客户端未初始化") + + target_url = full_url if full_url.startswith("http") else f"http://{full_url}" + + # 记录请求信息 + logger.info(f"代理请求: {request.method} {target_url}") + + try: + # 准备请求头(移除可能导致问题的头部) + headers = dict(request.headers) + headers.pop('host', None) + headers.pop('content-length', None) + + response = None + + # 处理不同的HTTP方法 + if request.method == "GET": + response = await http_client.get( + target_url, + headers=headers, + params=request.query_params + ) + + elif request.method == "POST": + content_type = request.headers.get('content-type', '') + + if 'application/json' in content_type: + json_data = await request.json() + response = await http_client.post( + target_url, + json=json_data, + headers=headers + ) + elif 'application/x-www-form-urlencoded' in content_type: + form_data = dict(await request.form()) + response = await http_client.post( + target_url, + data=form_data, + headers=headers + ) + elif 'multipart/form-data' in content_type: + form = await request.form() + files = {} + fields = {} + + for key in form: + if isinstance(form[key], UploadFile): + files[key] = (form[key].filename, await form[key].read()) + else: + fields[key] = form[key] + + response = await http_client.post( + target_url, + data=fields, + files=files, + headers=headers + ) + else: + # 处理原始数据 + body = await request.body() + response = await http_client.post( + target_url, + content=body, + headers=headers + ) + + elif request.method == "PUT": + body = await request.body() + response = await http_client.put( + target_url, + content=body, + headers=headers + ) + + elif request.method == "DELETE": + response = await http_client.delete( + target_url, + headers=headers + ) + + else: + raise HTTPException(status_code=405, detail="不支持的HTTP方法") + + # 准备响应头 + response_headers = dict(response.headers) + # 移除可能导致问题的响应头 + response_headers.pop('content-encoding', None) + response_headers.pop('transfer-encoding', None) + + # 返回流式响应 + return ProxyStreamingResponse( + stream_response_with_cleanup(response), + status_code=response.status_code, + headers=response_headers, + media_type=response_headers.get('content-type', 'application/octet-stream') + ) + + except httpx.TimeoutException: + logger.error(f"请求超时: {target_url}") + raise HTTPException(status_code=504, detail="请求超时") + except httpx.ConnectError: + logger.error(f"连接失败: {target_url}") + raise HTTPException(status_code=502, detail="无法连接到目标服务器") + except httpx.RequestError as e: + logger.error(f"请求错误: {e}") + raise HTTPException(status_code=500, detail=f"请求失败: {str(e)}") + except Exception as e: + logger.error(f"未知错误: {e}") + raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}") + +@app.get("/health") +async def health_check(): + """健康检查端点""" + memory_usage = MemoryMonitor.get_memory_usage() + return { + "status": "healthy", + "memory_usage_mb": round(memory_usage, 2), + "timestamp": time.time() + } + +@app.post("/admin/cleanup") +async def manual_cleanup(): + """手动触发清理""" + await MemoryMonitor.cleanup_resources() + memory_usage = MemoryMonitor.get_memory_usage() + return { + "message": "清理完成", + "memory_usage_mb": round(memory_usage, 2) + } + +if __name__ == "__main__": + import uvicorn + + # 启动服务器 + uvicorn.run( + app, + host="0.0.0.0", + port=8000, + log_level="info", + access_log=True + ) diff --git a/proxy/requirements.txt b/proxy/requirements.txt new file mode 100644 index 0000000..dc60ca5 --- /dev/null +++ b/proxy/requirements.txt @@ -0,0 +1,4 @@ +fastapi +httpx +uvicorn +psutil \ No newline at end of file diff --git a/proxy/start_proxy.py b/proxy/start_proxy.py new file mode 100644 index 0000000..c246939 --- /dev/null +++ b/proxy/start_proxy.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +代理服务器启动脚本 +提供配置管理和监控功能 +""" + +import argparse +import asyncio +import logging +import signal +import sys +from pathlib import Path + +import uvicorn + +# 添加当前目录到Python路径 +sys.path.insert(0, str(Path(__file__).parent)) + +from proxy import app, MemoryMonitor + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), + logging.FileHandler('proxy.log') + ] +) + +logger = logging.getLogger(__name__) + +class ProxyServer: + """代理服务器管理器""" + + def __init__(self, host="0.0.0.0", port=8000, workers=1): + self.host = host + self.port = port + self.workers = workers + self.server = None + + async def start(self): + """启动服务器""" + logger.info(f"启动代理服务器: {self.host}:{self.port}") + + config = uvicorn.Config( + app, + host=self.host, + port=self.port, + workers=self.workers, + log_level="info", + access_log=True, + reload=False + ) + + self.server = uvicorn.Server(config) + + # 设置信号处理 + signal.signal(signal.SIGINT, self._signal_handler) + signal.signal(signal.SIGTERM, self._signal_handler) + + try: + await self.server.serve() + except KeyboardInterrupt: + logger.info("收到中断信号,正在关闭服务器...") + except Exception as e: + logger.error(f"服务器启动失败: {e}") + raise + + def _signal_handler(self, signum, frame): + """信号处理器""" + logger.info(f"收到信号 {signum},准备关闭服务器...") + if self.server: + self.server.should_exit = True + + async def stop(self): + """停止服务器""" + if self.server: + self.server.should_exit = True + logger.info("服务器已停止") + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="代理服务器") + parser.add_argument("--host", default="0.0.0.0", help="绑定主机地址") + parser.add_argument("--port", type=int, default=8000, help="绑定端口") + parser.add_argument("--workers", type=int, default=1, help="工作进程数") + parser.add_argument("--debug", action="store_true", help="启用调试模式") + + args = parser.parse_args() + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + + # 创建并启动服务器 + server = ProxyServer(args.host, args.port, args.workers) + + try: + asyncio.run(server.start()) + except KeyboardInterrupt: + logger.info("服务器已停止") + except Exception as e: + logger.error(f"服务器运行出错: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/readme.md b/readme.md index f804231..89699e8 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,260 @@ +# 🎬 DrPlayer - 新一代智能视频播放器 + +
+ +![DrPlayer Logo](https://img.shields.io/badge/DrPlayer-2.0-blue?style=for-the-badge&logo=play&logoColor=white) +[![Vue 3](https://img.shields.io/badge/Vue-3.x-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white)](https://vuejs.org/) +[![Vite](https://img.shields.io/badge/Vite-5.x-646CFF?style=for-the-badge&logo=vite&logoColor=white)](https://vitejs.dev/) +[![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE) +[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=for-the-badge&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/hjdhnx/DrPlayer) + + +**一款功能强大、智能化的现代视频播放器,支持多格式播放、智能跳过、直播功能等特色功能** + +[🚀 在线演示](https://drplayer.playdreamer.cn/) | [📖 文档](https://hipy.playdreamer.cn/) | [🐛 问题反馈](https://github.com/hjdhnx/DrPlayer/issues) + +
+ +## ✨ 核心特色 + +### 🎯 智能播放体验 +- **🔄 双播放器引擎**:原生HTML5播放器 + ArtPlayer高级播放器,智能切换最佳播放方案 +- **🎬 智能跳过功能**:可配置的片头片尾自动跳过,告别重复内容,直达精彩部分 +- **⚡ 自动连播**:智能识别剧集,支持自动播放下一集,可自定义倒计时提醒 +- **📍 断点续播**:自动记录播放进度,随时随地继续观看 + +### 🌐 强大的兼容性 +- **📹 多格式支持**:完美支持 HLS (m3u8)、FLV、DASH (mpd)、MP4 等主流视频格式 +- **🛡️ CSP绕过技术**:智能绕过内容安全策略限制,大幅提升视频播放成功率 +- **🔍 智能格式检测**:自动识别视频格式并选择最佳播放策略 +- **📱 响应式设计**:完美适配桌面端、平板和移动设备 + +### 📺 丰富的直播功能 +- **🌍 海量直播源**:内置丰富的直播频道资源,包含4K频道、体育赛事、新闻资讯等 +- **📋 智能分组管理**:支持频道分类管理,快速定位想看的内容 +- **🔧 多格式解析**:支持 M3U8、M3U、TXT 等多种直播源格式 + +### ⚙️ 专业级功能 +- **🎛️ 多倍速播放**:支持 0.5x - 5x 多档位播放速度调节 +- **🎨 现代化UI**:基于 Arco Design 的精美界面设计 +- **🔧 高度可配置**:丰富的设置选项,满足不同用户需求 +- **🚀 性能优化**:基于 Vue 3 + Vite 的现代化架构,启动快速,运行流畅 + +## 🛠️ 技术栈 + +### 前端框架 +- **Vue 3** - 渐进式 JavaScript 框架 +- **Vite** - 下一代前端构建工具 +- **Pinia** - Vue 3 状态管理库 +- **Vue Router** - 官方路由管理器 + +### UI 组件库 +- **Arco Design Vue** - 企业级设计语言和组件库 +- **ECharts** - 数据可视化图表库 + +### 播放器技术 +- **ArtPlayer** - 现代化 HTML5 播放器 +- **HLS.js** - HLS 流媒体播放支持 +- **FLV.js** - FLV 格式播放支持 +- **Shaka Player** - DASH 流媒体播放支持 + +### 开发工具 +- **JavaScript** - 现代化的 JavaScript 开发 +- **ESLint** - 代码质量检查 +- **Prettier** - 代码格式化工具 + +## 🚀 快速开始 + +### 环境要求 +- Node.js >= 16.0.0 +- pnpm >= 7.0.0 (推荐) 或 npm >= 8.0.0 + +### 安装依赖 + +```bash +# 使用 pnpm (推荐) +pnpm install + +# 或使用 npm +npm install +``` + +### 开发环境 + +```bash +# 启动开发服务器 +pnpm dev + +# 或使用 npm +npm run dev +``` + +访问 `http://localhost:5173` 即可看到应用运行效果。 + +### 生产构建 + +```bash +# 构建生产版本 +pnpm build + +# 预览生产构建 +pnpm preview +``` + +## 📦 部署指南 + +### Vercel 部署 (推荐) +1. Fork 本项目到你的 GitHub +2. 在 [Vercel](https://vercel.com) 中导入项目 +3. 自动部署完成 + +### Nginx 部署 +```bash +# 构建项目 +pnpm build + +# 将 dist 目录部署到 Nginx 服务器 +# 配置 Nginx 支持 SPA 路由 +``` + +详细部署教程请参考:[部署指南](https://juejin.cn/post/7301193497247727652) + +### 解决刷新404问题 +参考:[解决vercel项目刷新404问题](https://juejin.cn/post/7358336719165554740) + +## 🎮 使用说明 + +### 基本播放 +1. 在播放器中输入视频链接 +2. 选择播放器类型(默认播放器 或 ArtPlayer) +3. 点击播放即可开始观看 + +### 智能跳过设置 +1. 点击播放器设置按钮 +2. 配置片头跳过时长(默认90秒) +3. 配置片尾跳过时长(默认90秒) +4. 保存设置后自动生效 + +### 直播功能 +1. 点击直播选项卡 +2. 选择频道分组 +3. 点击频道即可开始观看直播 + +## 🔧 配置说明 + +### 播放器配置 +```javascript +// 播放器选项配置 +const playerOptions = { + hls: { + maxBufferLength: 600, + liveSyncDurationCount: 10, + }, + flv: { + enableWorker: false, + enableStashBuffer: false, + autoCleanupSourceBuffer: true, + } +} +``` + +### CSP 绕过配置 +```javascript +// CSP绕过配置 +export const CSP_BYPASS_CONFIG = { + enabled: true, + referrerPolicy: 'no-referrer', + autoBypass: true, + autoRetry: true +} +``` + +## 🤝 贡献指南 + +我们欢迎所有形式的贡献!请查看 [贡献指南](CONTRIBUTING.md) 了解详细信息。 + +### 开发流程 +1. Fork 本项目 +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 创建 Pull Request + +## 📄 许可证 + +本项目基于 MIT 许可证开源 - 查看 [LICENSE](LICENSE) 文件了解详细信息。 + +## 🙏 致谢 + +- [Vue.js](https://vuejs.org/) - 渐进式 JavaScript 框架 +- [ArtPlayer](https://artplayer.org/) - 现代化 HTML5 播放器 +- [Arco Design](https://arco.design/) - 企业级设计语言 +- [Vite](https://vitejs.dev/) - 下一代前端构建工具 + +## 📞 联系我们 + +- 项目主页:[https://drplayer.playdreamer.cn/](https://drplayer.playdreamer.cn/) +- 文档站点:[https://hipy.playdreamer.cn/](https://hipy.playdreamer.cn/) +- 问题反馈:[GitHub Issues](https://github.com/hjdhnx/DrPlayer/issues) + +--- + +
+ +**如果这个项目对你有帮助,请给它一个 ⭐️** + +Made with ❤️ by DrPlayer Team + +
+ +--- + +## 📚 原始开发笔记 + +以下是项目开发过程中的技术笔记和参考资料: + +### 依赖安装记录 ```shell -yarn create vite -yarn add primevue primeicons -yarn add unplugin-vue-components -yarn add @primevue/auto-import-resolver -yarn add primeflex -yarn add vue-router -yarn add pinia -``` -https://juejin.cn/post/7387581121519812617 \ No newline at end of file +pnpm create vite +pnpm add primevue primeicons +pnpm add unplugin-vue-components +pnpm add @primevue/auto-import-resolver +pnpm add @primevue/themes +pnpm add primeflex + +pnpm add vue-router +pnpm add pinia + +pnpm remove primevue primeicons @primevue/auto-import-resolver primeflex @primevue/themes +pnpm add --save-dev @arco-design/web-vue +pnpm add json-server +pnpm add axios +``` + +### 技术参考 +- [Vue 3 组合式 API 指南](https://juejin.cn/post/7387581121519812617) +- [Arco Design Vue 组件库](https://arco.design/vue/component/layout) + +### 注意事项 +- package.json 需要注意:如果有 `type:'module'` 需要删除 +- json-server版本号只能 `^0.17.4`,不然不支持middleware + +### 实用脚本 +图标全选加购脚本: +```javascript +var span = document.querySelectorAll('.icon-cover'); +for (var i = 0, len = span.length; i < len; i++) { + console.log(span[i].querySelector('span').click()); +} +``` + +### 部署相关 +- [部署教程](https://juejin.cn/post/7301193497247727652) +- [解决vercel项目刷新404问题](https://juejin.cn/post/7358336719165554740) + +### 演示地址 +- 主站:[https://drplayer.playdreamer.cn/](https://drplayer.playdreamer.cn/) +- 文档:[https://hipy.playdreamer.cn/](https://hipy.playdreamer.cn/) + +### AI 服务 +[AI加量包购买](https://www.trae.ai/account-setting?purchase=1#usage)