diff --git a/README.md b/README.md index febb078..38f6d25 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ 本项目自「MoonTV」演进而来,为其二创/继承版本,持续维护与改进功能与体验。保留并致谢原作者与社区贡献者;如有授权或版权问题请联系以处理。目标:在原作基础上提供更易部署、更友好、更稳定的体验。 -> **🔔 重要变更通知**:应用户社区的宝贵建议,为确保项目的长期稳定运行和合规性,我们已将内置的视频源移除。现在用户需要自行配置资源站以使用本应用的完整功能。我们提供了经过测试的推荐配置文件,让您能够快速上手使用(具体配置文件见README.md内容底部)。 +> **🔔 重要变更通知**:应用户社区的宝贵建议,为确保项目的长期稳定运行和合规性,我们已将内置的视频源移除。现在用户需要自行配置资源站以使用本应用的完整功能。我们提供了经过测试的推荐配置文件,让您能够快速上手使用(具体配置文件见 README.md 内容底部)。 ## ✨ 功能特性 @@ -1035,6 +1035,32 @@ KatelyaTV 支持标准的苹果 CMS V10 API 格式。 目前该项目可以配合 [OrionTV](https://github.com/zimplexing/OrionTV) 在 Android TV 上使用,可以直接作为 OrionTV 后端 +### 🆕 v0.5.0-katelya 修复说明 + +**修复了 OrionTV 客户端无法播放的问题**: + +- **✅ 新增 CORS 支持**:为所有 API 路由添加了跨域请求头部,解决 OrionTV 客户端访问问题 +- **✅ 修复认证拦截**:调整了中间件配置,确保 OrionTV 必需的 API 路由不被认证系统拦截 +- **✅ 兼容性优化**:优化了搜索、详情、图片代理等关键 API 的响应头部 + +**如果你之前遇到"OrionTV 显示了资源但点击无法播放"的问题,现在应该已经解决了!** + +### 📱 OrionTV 配置方法 + +1. **下载 OrionTV 客户端**:在 Android TV 上安装 OrionTV 应用 +2. **配置 API 地址**:在 OrionTV 中填入你的 KatelyaTV 部署地址 +3. **输入密码**:填写你设置的 PASSWORD 环境变量 +4. **测试播放**:尝试搜索和播放视频 + +### 🔍 故障排除 + +如果还有播放问题,请检查: + +- 确保你的 KatelyaTV 版本是 v0.5.0-katelya 或更新版本 +- 确认已正确配置视频源(参考本文档的配置文件说明) +- 检查网络连接和防火墙设置 +- 确保密码配置正确 + 暂时收藏夹与播放记录和网页端隔离,后续会支持同步用户数据 ## 🗓️ Roadmap diff --git a/public/sw.js b/public/sw.js index ddfe6d0..4a35b7a 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,n={};const s=(s,i)=>(s=new URL(s+".js",i).href,n[s]||new Promise(n=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=n,document.head.appendChild(e)}else e=s,importScripts(s),n()}).then(()=>{let e=n[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e}));self.define=(i,c)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(n[a])return;let d={};const t=e=>s(e,a),r={module:{uri:a},exports:d,require:t};n[a]=Promise.all(i.map(e=>r[e]||t(e))).then(e=>(c(...e),d))}}define(["./workbox-e9849328"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"80b388c46e04a40635a9fe2b8cfc2a01"},{url:"/_next/static/chunks/110-cb68faf0f47f94e5.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/154-de4a84fd5b2e0100.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/29-0844689411ca7d55.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/459-6bec40a8423cc309.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/51b697cb-f464f3017ac1ea30.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/682-d1dca8d17a3a8e6f.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/900-fb094d8873768e88.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/967-217cdcb80ae3beeb.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/998-568996670b543597.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/_not-found/page-ac328df06cf68f14.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/admin/page-d0def26e413c060d.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/douban/page-2d0023184aa37aff.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/layout-bd0bfbfdb401e15f.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/login/page-6d62f8fe1814a4fb.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/page-6a58e37ab3250691.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/play/page-cbcfbf4a92cde119.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/search/page-63fe30b91e0539a7.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/main-95de9e33689c098a.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"nlHzYUnQridQuWdwd-dlI"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/3ec63c712b6e4432.css",revision:"3ec63c712b6e4432"},{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:"/_next/static/nlHzYUnQridQuWdwd-dlI/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/nlHzYUnQridQuWdwd-dlI/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{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:n,event:s,state:i})=>n&&"opaqueredirect"===n.type?new Response(n.body,{status:200,statusText:"OK",headers:n.headers}):n}]}),"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 n=e.pathname;return!n.startsWith("/api/auth/")&&!!n.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 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} didn’t 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 r=e=>c(e,i),o={module:{uri:i},exports:t,require:r};s[i]=Promise.all(n.map(e=>o[e]||r(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:"a3822a577cf1faa67c250e814206fd06"},{url:"/_next/static/9ychmD0qblND414bSgAFr/_buildManifest.js",revision:"046380ae5bc74b46b6d5eac3eed65355"},{url:"/_next/static/9ychmD0qblND414bSgAFr/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/110-cb68faf0f47f94e5.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/154-de4a84fd5b2e0100.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/29-0844689411ca7d55.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/459-6bec40a8423cc309.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/51b697cb-f464f3017ac1ea30.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/682-d1dca8d17a3a8e6f.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/900-fb094d8873768e88.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/967-217cdcb80ae3beeb.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/998-568996670b543597.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/_not-found/page-ac328df06cf68f14.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/admin/page-d0def26e413c060d.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/douban/page-2d0023184aa37aff.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/layout-bd0bfbfdb401e15f.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/login/page-6d62f8fe1814a4fb.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/page-6a58e37ab3250691.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/play/page-cbcfbf4a92cde119.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/search/page-63fe30b91e0539a7.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/app/warning/page-11cba4cf9332a238.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/c72274ce-06682d6fc8197e6d.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/da9543df-bf6da1a431d8604f.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/framework-6e06c675866dc992.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/main-65b8f7904c26b8df.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/main-app-dbd320e104e1a5dc.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/pages/_app-792b631a362c29e1.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/pages/_error-9fde6601392a2a99.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-17170f1d90853b2d.js",revision:"9ychmD0qblND414bSgAFr"},{url:"/_next/static/css/23100062f5d4aac0.css",revision:"23100062f5d4aac0"},{url:"/_next/static/css/275ed64cc4367444.css",revision:"275ed64cc4367444"},{url:"/_next/static/css/3ec63c712b6e4432.css",revision:"3ec63c712b6e4432"},{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")}); diff --git a/src/app/api/detail/route.ts b/src/app/api/detail/route.ts index 6d3c71b..74f86b9 100644 --- a/src/app/api/detail/route.ts +++ b/src/app/api/detail/route.ts @@ -1,21 +1,29 @@ import { NextResponse } from 'next/server'; import { getAvailableApiSites, getCacheTime } from '@/lib/config'; +import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors'; import { getDetailFromApi } from '@/lib/downstream'; export const runtime = 'edge'; +// 处理OPTIONS预检请求(OrionTV客户端需要) +export async function OPTIONS() { + return handleOptionsRequest(); +} + export async function GET(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id'); const sourceCode = searchParams.get('source'); if (!id || !sourceCode) { - return NextResponse.json({ error: '缺少必要参数' }, { status: 400 }); + const response = NextResponse.json({ error: '缺少必要参数' }, { status: 400 }); + return addCorsHeaders(response); } if (!/^[\w-]+$/.test(id)) { - return NextResponse.json({ error: '无效的视频ID格式' }, { status: 400 }); + const response = NextResponse.json({ error: '无效的视频ID格式' }, { status: 400 }); + return addCorsHeaders(response); } try { @@ -23,23 +31,26 @@ export async function GET(request: Request) { const apiSite = apiSites.find((site) => site.key === sourceCode); if (!apiSite) { - return NextResponse.json({ error: '无效的API来源' }, { status: 400 }); + const response = NextResponse.json({ error: '无效的API来源' }, { status: 400 }); + return addCorsHeaders(response); } const result = await getDetailFromApi(apiSite, id); const cacheTime = await getCacheTime(); - return NextResponse.json(result, { + const response = NextResponse.json(result, { headers: { 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`, 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`, 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`, }, }); + return addCorsHeaders(response); } catch (error) { - return NextResponse.json( + const response = NextResponse.json( { error: (error as Error).message }, { status: 500 } ); + return addCorsHeaders(response); } } diff --git a/src/app/api/image-proxy/route.ts b/src/app/api/image-proxy/route.ts index 493665c..436188d 100644 --- a/src/app/api/image-proxy/route.ts +++ b/src/app/api/image-proxy/route.ts @@ -1,14 +1,22 @@ import { NextResponse } from 'next/server'; +import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors'; + export const runtime = 'edge'; +// 处理OPTIONS预检请求(OrionTV客户端需要) +export async function OPTIONS() { + return handleOptionsRequest(); +} + // OrionTV 兼容接口 export async function GET(request: Request) { const { searchParams } = new URL(request.url); const imageUrl = searchParams.get('url'); if (!imageUrl) { - return NextResponse.json({ error: 'Missing image URL' }, { status: 400 }); + const response = NextResponse.json({ error: 'Missing image URL' }, { status: 400 }); + return addCorsHeaders(response); } try { @@ -21,19 +29,21 @@ export async function GET(request: Request) { }); if (!imageResponse.ok) { - return NextResponse.json( + const response = NextResponse.json( { error: imageResponse.statusText }, { status: imageResponse.status } ); + return addCorsHeaders(response); } const contentType = imageResponse.headers.get('content-type'); if (!imageResponse.body) { - return NextResponse.json( + const response = NextResponse.json( { error: 'Image response has no body' }, { status: 500 } ); + return addCorsHeaders(response); } // 创建响应头 @@ -48,14 +58,16 @@ export async function GET(request: Request) { headers.set('Vercel-CDN-Cache-Control', 'public, s-maxage=15720000'); // 直接返回图片流 - return new Response(imageResponse.body, { + const response = new Response(imageResponse.body, { status: 200, headers, }); + return addCorsHeaders(response); } catch (error) { - return NextResponse.json( + const response = NextResponse.json( { error: 'Error fetching image' }, { status: 500 } ); + return addCorsHeaders(response); } } diff --git a/src/app/api/search/one/route.ts b/src/app/api/search/one/route.ts index 8af2506..4535dad 100644 --- a/src/app/api/search/one/route.ts +++ b/src/app/api/search/one/route.ts @@ -1,10 +1,16 @@ import { NextResponse } from 'next/server'; import { getAvailableApiSites, getCacheTime } from '@/lib/config'; +import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors'; import { searchFromApi } from '@/lib/downstream'; export const runtime = 'edge'; +// 处理OPTIONS预检请求(OrionTV客户端需要) +export async function OPTIONS() { + return handleOptionsRequest(); +} + // OrionTV 兼容接口 export async function GET(request: Request) { const { searchParams } = new URL(request.url); @@ -13,7 +19,7 @@ export async function GET(request: Request) { if (!query || !resourceId) { const cacheTime = await getCacheTime(); - return NextResponse.json( + const response = NextResponse.json( { result: null, error: '缺少必要参数: q 或 resourceId' }, { headers: { @@ -23,6 +29,7 @@ export async function GET(request: Request) { }, } ); + return addCorsHeaders(response); } const apiSites = await getAvailableApiSites(); @@ -31,13 +38,14 @@ export async function GET(request: Request) { // 根据 resourceId 查找对应的 API 站点 const targetSite = apiSites.find((site) => site.key === resourceId); if (!targetSite) { - return NextResponse.json( + const response = NextResponse.json( { error: `未找到指定的视频源: ${resourceId}`, result: null, }, { status: 404 } ); + return addCorsHeaders(response); } const results = await searchFromApi(targetSite, query); @@ -45,15 +53,16 @@ export async function GET(request: Request) { const cacheTime = await getCacheTime(); if (result.length === 0) { - return NextResponse.json( + const response = NextResponse.json( { error: '未找到结果', result: null, }, { status: 404 } ); + return addCorsHeaders(response); } else { - return NextResponse.json( + const response = NextResponse.json( { results: result }, { headers: { @@ -63,14 +72,16 @@ export async function GET(request: Request) { }, } ); + return addCorsHeaders(response); } } catch (error) { - return NextResponse.json( + const response = NextResponse.json( { error: '搜索失败', result: null, }, { status: 500 } ); + return addCorsHeaders(response); } } diff --git a/src/app/api/search/resources/route.ts b/src/app/api/search/resources/route.ts index fc351fd..de5281e 100644 --- a/src/app/api/search/resources/route.ts +++ b/src/app/api/search/resources/route.ts @@ -1,23 +1,31 @@ import { NextResponse } from 'next/server'; import { getAvailableApiSites, getCacheTime } from '@/lib/config'; +import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors'; export const runtime = 'edge'; +// 处理OPTIONS预检请求(OrionTV客户端需要) +export async function OPTIONS() { + return handleOptionsRequest(); +} + // OrionTV 兼容接口 export async function GET() { try { const apiSites = await getAvailableApiSites(); const cacheTime = await getCacheTime(); - return NextResponse.json(apiSites, { + const response = NextResponse.json(apiSites, { headers: { 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`, 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`, 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`, }, }); + return addCorsHeaders(response); } catch (error) { - return NextResponse.json({ error: '获取资源失败' }, { status: 500 }); + const response = NextResponse.json({ error: '获取资源失败' }, { status: 500 }); + return addCorsHeaders(response); } } diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index a9c64c3..62e2b70 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -1,17 +1,23 @@ import { NextResponse } from 'next/server'; import { getAvailableApiSites, getCacheTime } from '@/lib/config'; +import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors'; import { searchFromApi } from '@/lib/downstream'; export const runtime = 'edge'; +// 处理OPTIONS预检请求(OrionTV客户端需要) +export async function OPTIONS() { + return handleOptionsRequest(); +} + export async function GET(request: Request) { const { searchParams } = new URL(request.url); const query = searchParams.get('q'); if (!query) { const cacheTime = await getCacheTime(); - return NextResponse.json( + const response = NextResponse.json( { results: [] }, { headers: { @@ -21,6 +27,7 @@ export async function GET(request: Request) { }, } ); + return addCorsHeaders(response); } const apiSites = await getAvailableApiSites(); @@ -31,7 +38,7 @@ export async function GET(request: Request) { const flattenedResults = results.flat(); const cacheTime = await getCacheTime(); - return NextResponse.json( + const response = NextResponse.json( { results: flattenedResults }, { headers: { @@ -41,7 +48,9 @@ export async function GET(request: Request) { }, } ); + return addCorsHeaders(response); } catch (error) { - return NextResponse.json({ error: '搜索失败' }, { status: 500 }); + const response = NextResponse.json({ error: '搜索失败' }, { status: 500 }); + return addCorsHeaders(response); } } diff --git a/src/lib/cors.ts b/src/lib/cors.ts new file mode 100644 index 0000000..1560cca --- /dev/null +++ b/src/lib/cors.ts @@ -0,0 +1,32 @@ +// CORS工具函数,用于为OrionTV客户端提供跨域支持 +export function createCorsHeaders(): Headers { + const headers = new Headers(); + + // 设置CORS头部,允许OrionTV客户端跨域访问 + headers.set('Access-Control-Allow-Origin', '*'); + headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With'); + headers.set('Access-Control-Max-Age', '86400'); // 24小时 + + return headers; +} + +// 为NextResponse添加CORS头部 +export function addCorsHeaders(response: Response): Response { + const corsHeaders = createCorsHeaders(); + + // 将CORS头部添加到现有响应头部中 + corsHeaders.forEach((value, key) => { + response.headers.set(key, value); + }); + + return response; +} + +// 处理OPTIONS预检请求 +export function handleOptionsRequest(): Response { + return new Response(null, { + status: 200, + headers: createCorsHeaders(), + }); +} diff --git a/src/middleware.ts b/src/middleware.ts index b672d79..add1771 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean { // 配置middleware匹配规则 export const config = { matcher: [ - '/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config).*)', + '/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/search|api/detail|api/image-proxy).*)', ], };