feat: Add TVBox configuration support and management interface

- Introduced a new configuration page for TVBox with JSON and Base64 format options.
- Updated API endpoints for TVBox configuration retrieval.
- Enhanced the sidebar navigation to link to the new configuration page.
- Improved error handling and user feedback for configuration copying.
- Added detailed usage instructions and feature descriptions in the documentation.
- Fixed issues related to deployment and static generation.
This commit is contained in:
katelya
2025-09-03 19:54:58 +08:00
parent 2294f1b066
commit 1ca36f7454
7 changed files with 192 additions and 245 deletions
+24
View File
@@ -2,6 +2,30 @@
本文档记录 KatelyaTV 项目的重要更新和功能变更。
## [0.5.1] - 2025-09-03
### 🎉 新增功能
- 📺 **TVBox 兼容支持**
- 新增 TVBox 配置接口,支持标准 JSON 格式配置
- 提供直观的配置管理界面 (`/config` 页面)
- 支持 JSON 和 Base64 两种配置格式
- 内置视频解析接口,支持多种视频平台
- 完全兼容 TVBox 及其衍生应用
- 自动同步 KatelyaTV 配置的所有视频源
### 🔧 技术改进
- 新增 `/api/tvbox` API 端点,提供 TVBox 标准配置
- 新增 `/api/parse` 视频解析接口
- 新增 TVBox 配置页面组件,支持动态格式切换
- 添加 CORS 跨域支持,确保 TVBox 应用正常访问
- 完善的错误处理和用户提示
- 新增详细的 TVBox 使用文档
### 🐛 问题修复
- 修复 Cloudflare Pages 部署时的 Suspense 边界问题
- 解决 Next.js 静态生成时的 useSearchParams 错误
- 优化构建配置,确保跨平台部署兼容性
## [0.5.0] - 2025-09-02
### 🎉 新增功能
+6 -5
View File
@@ -78,14 +78,15 @@ KatelyaTV 新增了 TVBox 配置接口,可以将您的视频源导入到各种
### 🚀 快速使用
1. **访问配置页面**:在 KatelyaTV 中点击侧边栏的"TVBox 配置"
2. **复制配置链接**:选择适合的格式并复制链接
3. **导入到 TVBox**:在 TVBox 应用中导入配置链接
1. **访问配置页面**:在 KatelyaTV 中点击侧边栏的"TVBox 配置"或访问 `/config` 页面
2. **选择格式类型**:在页面中选择 JSON 或 Base64 格式
3. **复制配置链接**:点击复制按钮获取配置链接
4. **导入到 TVBox**:在 TVBox 应用中导入配置链接
### 🔗 API 端点
- **JSON 配置**`https://your-domain.com/api/tvbox`
- **Base64 配置**`https://your-domain.com/api/tvbox?format=txt`
- **JSON 配置**`https://your-domain.com/api/tvbox?format=json`
- **Base64 配置**`https://your-domain.com/api/tvbox?format=base64`
- **视频解析**`https://your-domain.com/api/parse?url={视频地址}`
> 📖 详细使用说明请查看:[TVBox 配置指南](docs/TVBOX.md)
+24 -8
View File
@@ -11,23 +11,30 @@ KatelyaTV 现在支持 TVBox 配置接口,可以将您的视频源直接导入
在 KatelyaTV 网站中,点击左侧导航栏的"TVBox 配置"菜单,或直接访问:
```
https://your-domain.com/tvbox
https://your-domain.com/config
```
### 2. 复制配置链接
### 2. 生成配置链接
选择以下任一配置格式
在配置页面中
**JSON 格式(推荐):**
1. **选择格式类型**
- **JSON 格式(推荐)**:标准的 JSON 配置文件,便于调试和查看
- **Base64 格式**:编码后的配置,适合某些特殊环境
2. **复制配置链接**:点击"复制"按钮,系统会自动生成对应格式的配置链接
**JSON 格式:**
```
https://your-domain.com/api/tvbox
https://your-domain.com/api/tvbox?format=json
```
**Base64 格式:**
```
https://your-domain.com/api/tvbox?format=txt
https://your-domain.com/api/tvbox?format=base64
```
### 3. 导入到 TVBox
@@ -39,7 +46,16 @@ https://your-domain.com/api/tvbox?format=txt
## 🔧 配置说明
### 支持的功能
### 🖥️ 配置页面功能
KatelyaTV 提供了直观的 TVBox 配置管理界面:
- **格式切换**:支持 JSON 和 Base64 两种格式切换
- **一键复制**:点击复制按钮快速获取配置链接
- **实时生成**:根据当前网站配置实时生成最新的 TVBox 配置
- **使用指南**:页面内置详细的使用说明和功能介绍
### 📋 支持的功能
- ✅ 自动同步 KatelyaTV 的所有视频源
- ✅ 支持搜索功能
@@ -86,7 +102,7 @@ https://your-domain.com/api/parse?url={视频地址}
- `format`: 返回格式
- `json`(默认):返回 JSON 格式配置
- `txt`:返回 Base64 编码的配置
- `base64`:返回 Base64 编码的配置
**响应:**
+1 -1
View File
@@ -1 +1 @@
if(!self.define){let e,s={};const c=(c,n)=>(c=new URL(c+".js",n).href,s[c]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=c,e.onload=s,document.head.appendChild(e)}else e=c,importScripts(c),s()}).then(()=>{let e=s[c];if(!e)throw new Error(`Module ${c} didnt register its module`);return e}));self.define=(n,a)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(s[i])return;let t={};const o=e=>c(e,i),r={module:{uri:i},exports:t,require:o};s[i]=Promise.all(n.map(e=>r[e]||o(e))).then(e=>(a(...e),t))}}define(["./workbox-e9849328"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"cec7c3688ab87f484a952cd9c53ebf66"},{url:"/_next/static/6L72pSo3cphEF2ldMfzS2/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/6L72pSo3cphEF2ldMfzS2/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/110-cb68faf0f47f94e5.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/154-de4a84fd5b2e0100.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/29-0844689411ca7d55.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/459-6bec40a8423cc309.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/51b697cb-f464f3017ac1ea30.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/682-d1dca8d17a3a8e6f.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/900-fb094d8873768e88.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/967-217cdcb80ae3beeb.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/998-568996670b543597.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/_not-found/page-ac328df06cf68f14.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/admin/page-d05d4621a6953d54.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/douban/page-2d0023184aa37aff.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/layout-bd0bfbfdb401e15f.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/login/page-6d62f8fe1814a4fb.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/page-6a58e37ab3250691.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/play/page-cbcfbf4a92cde119.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/search/page-63fe30b91e0539a7.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/main-65b8f7904c26b8df.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"6L72pSo3cphEF2ldMfzS2"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/5f97e94da3c9ad89.css",revision:"5f97e94da3c9ad89"},{url:"/_next/static/media/26a46d62cd723877-s.woff2",revision:"befd9c0fdfa3d8a645d5f95717ed6420"},{url:"/_next/static/media/55c55f0601d81cf3-s.woff2",revision:"43828e14271c77b87e3ed582dbff9f74"},{url:"/_next/static/media/581909926a08bbc8-s.woff2",revision:"f0b86e7c24f455280b8df606b89af891"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/97e0cb1ae144a2a9-s.woff2",revision:"e360c61c5bd8d90639fd4503c829c2dc"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/favicon.ico",revision:"c5de6e56c5664adda146825f75ea6ecf"},{url:"/icons/icon-192x192.png",revision:"4a56c090828a1ad254c903c7aec0389d"},{url:"/icons/icon-256x256.png",revision:"f6409eb1a001f754121e3a8281c0319c"},{url:"/icons/icon-384x384.png",revision:"f6efc3e357b9ffdf4e0d8c14b2ed0ac1"},{url:"/icons/icon-512x512.png",revision:"9c008cbbeb6a576fe07bb1284a83f4d2"},{url:"/logo.png",revision:"40de611b143c47c6291c7bdad2c959ca"},{url:"/manifest.json",revision:"7bd3dabc1cfbfe40f09577efca223d31"},{url:"/robots.txt",revision:"e2b2cd8514443456bc6fb9d77b3b1f3e"},{url:"/screenshot1.png",revision:"10572bfcea54dc93ac4c5f7c9057fc98"},{url:"/screenshot2.png",revision:"f815a8990973a221899976867365c239"},{url:"/screenshot3.png",revision:"49709e96345dfeeab1d8083821d4b44e"},{url:"/screenshot4.png",revision:"a76c751e41e37556048a487e4f8b8b1c"},{url:"/wechat.jpg",revision:"d0f601311802667cd6ca5a37dc69bfa7"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:c,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
if(!self.define){let e,s={};const a=(a,i)=>(a=new URL(a+".js",i).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didnt register its module`);return e}));self.define=(i,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let c={};const r=e=>a(e,t),o={module:{uri:t},exports:c,require:r};s[t]=Promise.all(i.map(e=>o[e]||r(e))).then(e=>(n(...e),c))}}define(["./workbox-e9849328"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"6358089a5359adbd7554fc2e1a465a46"},{url:"/_next/static/Pu_hBNLyBqiZzKJBWsatT/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/Pu_hBNLyBqiZzKJBWsatT/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/110-cb68faf0f47f94e5.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/154-de4a84fd5b2e0100.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/29-0844689411ca7d55.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/459-6bec40a8423cc309.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/51b697cb-f464f3017ac1ea30.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/682-d1dca8d17a3a8e6f.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/900-fb094d8873768e88.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/967-217cdcb80ae3beeb.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/998-568996670b543597.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/_not-found/page-ac328df06cf68f14.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/admin/page-d05d4621a6953d54.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/config/page-11f6321397ad65b1.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/douban/page-2d0023184aa37aff.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/layout-bd0bfbfdb401e15f.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/login/page-6d62f8fe1814a4fb.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/page-6a58e37ab3250691.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/play/page-cbcfbf4a92cde119.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/search/page-63fe30b91e0539a7.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/main-42adc5a723775cd8.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"Pu_hBNLyBqiZzKJBWsatT"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/6a4e8dcd24cf5997.css",revision:"6a4e8dcd24cf5997"},{url:"/_next/static/media/26a46d62cd723877-s.woff2",revision:"befd9c0fdfa3d8a645d5f95717ed6420"},{url:"/_next/static/media/55c55f0601d81cf3-s.woff2",revision:"43828e14271c77b87e3ed582dbff9f74"},{url:"/_next/static/media/581909926a08bbc8-s.woff2",revision:"f0b86e7c24f455280b8df606b89af891"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/97e0cb1ae144a2a9-s.woff2",revision:"e360c61c5bd8d90639fd4503c829c2dc"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/favicon.ico",revision:"c5de6e56c5664adda146825f75ea6ecf"},{url:"/icons/icon-192x192.png",revision:"4a56c090828a1ad254c903c7aec0389d"},{url:"/icons/icon-256x256.png",revision:"f6409eb1a001f754121e3a8281c0319c"},{url:"/icons/icon-384x384.png",revision:"f6efc3e357b9ffdf4e0d8c14b2ed0ac1"},{url:"/icons/icon-512x512.png",revision:"9c008cbbeb6a576fe07bb1284a83f4d2"},{url:"/logo.png",revision:"40de611b143c47c6291c7bdad2c959ca"},{url:"/manifest.json",revision:"7bd3dabc1cfbfe40f09577efca223d31"},{url:"/robots.txt",revision:"e2b2cd8514443456bc6fb9d77b3b1f3e"},{url:"/screenshot1.png",revision:"10572bfcea54dc93ac4c5f7c9057fc98"},{url:"/screenshot2.png",revision:"f815a8990973a221899976867365c239"},{url:"/screenshot3.png",revision:"49709e96345dfeeab1d8083821d4b44e"},{url:"/screenshot4.png",revision:"a76c751e41e37556048a487e4f8b8b1c"},{url:"/wechat.jpg",revision:"d0f601311802667cd6ca5a37dc69bfa7"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:i})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
+124
View File
@@ -0,0 +1,124 @@
'use client';
import { useCallback, useState } from 'react';
export const dynamic = 'force-dynamic';
export default function ConfigPage() {
const [copied, setCopied] = useState(false);
const [format, setFormat] = useState<'json' | 'base64'>('json');
const getConfigUrl = useCallback(() => {
if (typeof window === 'undefined') return '';
const baseUrl = window.location.origin;
return `${baseUrl}/api/tvbox?format=${format}`;
}, [format]);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(getConfigUrl());
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Copy failed silently
}
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-8">
TVBox
</h1>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
</h2>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
</label>
<select
value={format}
onChange={(e) => setFormat(e.target.value as 'json' | 'base64')}
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="json">JSON </option>
<option value="base64">Base64 </option>
</select>
</div>
<div className="flex items-center space-x-2">
<input
type="text"
readOnly
value={getConfigUrl()}
className="flex-1 p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
/>
<button
onClick={handleCopy}
className={`px-4 py-3 rounded-md font-medium transition-colors ${
copied
? 'bg-green-500 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
{copied ? '已复制' : '复制'}
</button>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
使
</h2>
<div className="space-y-4 text-gray-700 dark:text-gray-300">
<div>
<h3 className="font-semibold text-lg mb-2">1. </h3>
<p> JSON Base64 </p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">2. TVBox</h3>
<p> TVBox </p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">3. 使</h3>
<p> TVBox </p>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="space-y-2">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> TVBox</li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
+12 -230
View File
@@ -1,242 +1,24 @@
'use client';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import PageLayout from '@/components/PageLayout';
export const dynamic = 'force-dynamic';
export default function TVBoxPage() {
const [baseUrl, setBaseUrl] = useState('');
const [copySuccess, setCopySuccess] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
// 获取当前域名
setBaseUrl(window.location.origin);
}, []);
const handleCopy = async (text: string, type: string) => {
try {
await navigator.clipboard.writeText(text);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
} catch (err) {
// 降级方案:使用document.execCommand
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
setCopySuccess(type);
setTimeout(() => setCopySuccess(null), 2000);
}
};
const configs = [
{
name: 'TVBox JSON配置',
description: '直接返回JSON格式的配置文件,适用于支持在线配置的TVBox应用',
url: `${baseUrl}/api/tvbox`,
type: 'json'
},
{
name: 'TVBox Base64配置',
description: '返回Base64编码的配置文件,适用于大部分TVBox应用',
url: `${baseUrl}/api/tvbox?format=txt`,
type: 'base64'
}
];
// 重定向到新的配置页面
router.replace('/config');
}, [router]);
return (
<PageLayout>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
{/* 页面标题 */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
📺 TVBox配置接口
</h1>
<p className="text-lg text-gray-600 dark:text-gray-300">
KatelyaTV的视频源导入到TVBox应用中使用
</p>
</div>
{/* 功能介绍 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🎯
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-3">
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
KatelyaTV的所有视频源
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
TVBox标准JSON格式
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
Base64编码格式
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
CORS跨域支持
</span>
</div>
<div className="flex items-start space-x-3">
<span className="text-green-500 text-sm"></span>
<span className="text-gray-700 dark:text-gray-300">
</span>
</div>
</div>
</div>
</div>
{/* 配置链接 */}
<div className="space-y-6">
{configs.map((config) => (
<div
key={config.type}
className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6"
>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
{config.name}
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{config.description}
</p>
</div>
<button
onClick={() => handleCopy(config.url, config.type)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
copySuccess === config.type
? 'bg-green-500 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
{copySuccess === config.type ? '已复制!' : '复制链接'}
</button>
</div>
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<code className="text-sm text-gray-800 dark:text-gray-200 break-all">
{config.url}
</code>
</div>
</div>
))}
</div>
{/* 使用说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
📖 使
</h2>
<div className="space-y-4 text-gray-700 dark:text-gray-300">
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
1.
</h3>
<p className="text-sm ml-4">
"复制链接"
</p>
</div>
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
2. TVBox
</h3>
<p className="text-sm ml-4">
TVBox应用
</p>
</div>
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
3.
</h3>
<p className="text-sm ml-4">
KatelyaTV添加新的视频源时TVBox中刷新配置即可同步最新源站
</p>
</div>
</div>
</div>
{/* API参数说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🔧 API参数说明
</h2>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tr>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
format
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
json() txt(base64编码)
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
?format=txt
</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* 解析接口说明 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
🎬
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
KatelyaTV提供内置的视频解析接口
</p>
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<code className="text-sm text-gray-800 dark:text-gray-200">
{baseUrl}/api/parse?url=
</code>
</div>
<div className="mt-4 text-sm text-gray-600 dark:text-gray-400">
<p>TV</p>
</div>
</div>
</div>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<p className="text-gray-600 dark:text-gray-300">...</p>
</div>
</PageLayout>
</div>
);
}
+1 -1
View File
@@ -141,7 +141,7 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
{
icon: Settings,
label: 'TVBox配置',
href: '/tvbox',
href: '/config',
},
];