diff --git a/README.md b/README.md index 2cca7d2..8c18abd 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ dockge/komodo 等 docker compose UI 也有自动更新功能 | NEXT_PUBLIC_FLUID_SEARCH | 是否开启搜索接口流式输出 | true/ false | true | | NEXT_PUBLIC_PROXY_M3U8_TOKEN | M3U8 代理 API 鉴权 Token(外部播放器跳转时的鉴权token,不填为无鉴权) | 任意字符串 | (空) | | NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES | 弹幕缓存失效时间(分钟数,设为 0 时不缓存) | 0 或正整数 | 4320(3天) | +| ENABLE_TV_MODE | 是否启用 TV 模式;设为 false 后 /tv 不可访问,且不启动电视遥控 Socket.IO 监听 | true/false | true | | ENABLE_TVBOX_SUBSCRIBE | 是否启用 TVBOX 订阅功能 | true/false | false | | TVBOX_SUBSCRIBE_TOKEN | TVBOX 订阅 API 访问 Token,如启用TVBOX功能必须设置该项 | 任意字符串 | (空) | | TVBOX_BLOCKED_SOURCES | TVBOX 订阅屏蔽源列表(多个源用逗号分隔,匹配视频源的 key) | 逗号分隔的源 key | (空) | diff --git a/server.js b/server.js index c0de442..6196be4 100644 --- a/server.js +++ b/server.js @@ -17,6 +17,10 @@ function shouldInitSQLite() { return process.env.NEXT_PUBLIC_STORAGE_TYPE === 'd1' && !isCloudflare && process.env.MOONTV_LITE !== 'true'; } +function isTVModeEnabled() { + return process.env.ENABLE_TV_MODE !== 'false'; +} + function ensureSQLiteReady() { if (!shouldInitSQLite()) { return; @@ -820,19 +824,30 @@ app.prepare().then(async () => { let watchRoomServer = null; let tvRemoteServer = null; + let io = null; - const io = new Server(httpServer, { - path: '/socket.io', - cors: { - origin: '*', - methods: ['GET', 'POST'], - }, - }); + const tvModeEnabled = isTVModeEnabled(); + const shouldStartInternalWatchRoom = + watchRoomConfig.enabled && watchRoomConfig.serverType === 'internal'; - tvRemoteServer = new TVRemoteServer(io); - console.log('[TVRemote] Socket.IO remote server initialized'); + if (tvModeEnabled || shouldStartInternalWatchRoom) { + io = new Server(httpServer, { + path: '/socket.io', + cors: { + origin: '*', + methods: ['GET', 'POST'], + }, + }); + } - if (watchRoomConfig.enabled && watchRoomConfig.serverType === 'internal') { + if (tvModeEnabled && io) { + tvRemoteServer = new TVRemoteServer(io); + console.log('[TVRemote] Socket.IO remote server initialized'); + } else { + console.log('[TVRemote] TV mode disabled, remote server not initialized'); + } + + if (shouldStartInternalWatchRoom && io) { // 初始化观影室服务器 watchRoomServer = new WatchRoomServer(io); console.log('[WatchRoom] Socket.IO server initialized'); @@ -851,7 +866,11 @@ app.prepare().then(async () => { }) .listen(port, () => { console.log(`> Ready on http://${hostname}:${port}`); - console.log(`> Socket.IO ready on ws://${hostname}:${port}`); + if (io) { + console.log(`> Socket.IO ready on ws://${hostname}:${port}`); + } else { + console.log('> Socket.IO disabled'); + } }); const forceExit = (signal) => { diff --git a/src/app/api/server-config/route.ts b/src/app/api/server-config/route.ts index eacf716..a87f63a 100644 --- a/src/app/api/server-config/route.ts +++ b/src/app/api/server-config/route.ts @@ -36,6 +36,7 @@ export async function GET(request: NextRequest) { SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTVPlus', StorageType: 'localstorage', Version: CURRENT_VERSION, + TVModeEnabled: process.env.ENABLE_TV_MODE !== 'false', WatchRoom: watchRoomConfig, EnableOfflineDownload: process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true', DanmakuAutoLoadDefault: true, @@ -48,6 +49,7 @@ export async function GET(request: NextRequest) { SiteName: config.SiteConfig.SiteName, StorageType: storageType, Version: CURRENT_VERSION, + TVModeEnabled: process.env.ENABLE_TV_MODE !== 'false', WatchRoom: watchRoomConfig, EnableOfflineDownload: process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true', EnableRegistration: config.SiteConfig.EnableRegistration || false, diff --git a/src/app/api/tv-remote/devices/route.ts b/src/app/api/tv-remote/devices/route.ts index a260da6..799d6ec 100644 --- a/src/app/api/tv-remote/devices/route.ts +++ b/src/app/api/tv-remote/devices/route.ts @@ -1,12 +1,17 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { isTVModeEnabled } from '@/lib/tv-mode'; const { listTVRemoteDevices } = require('@/lib/tv-remote-hub'); export const runtime = 'nodejs'; export async function GET(request: NextRequest) { + if (!isTVModeEnabled()) { + return NextResponse.json({ error: 'TV 模式未启用' }, { status: 404 }); + } + const authInfo = getAuthInfoFromCookie(request); if (!authInfo?.username) { return NextResponse.json({ error: '未登录' }, { status: 401 }); diff --git a/src/app/api/tv-remote/key/route.ts b/src/app/api/tv-remote/key/route.ts index 4b4e11c..1e88d58 100644 --- a/src/app/api/tv-remote/key/route.ts +++ b/src/app/api/tv-remote/key/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { isTVModeEnabled } from '@/lib/tv-mode'; import type { TVRemoteKeyCommand } from '@/lib/tv-remote-types'; const { sendTVRemoteCommand } = require('@/lib/tv-remote-hub'); @@ -8,6 +9,10 @@ const { sendTVRemoteCommand } = require('@/lib/tv-remote-hub'); export const runtime = 'nodejs'; export async function POST(request: NextRequest) { + if (!isTVModeEnabled()) { + return NextResponse.json({ error: 'TV 模式未启用' }, { status: 404 }); + } + const authInfo = getAuthInfoFromCookie(request); if (!authInfo?.username) { return NextResponse.json({ error: '未登录' }, { status: 401 }); diff --git a/src/app/api/tv-remote/text/route.ts b/src/app/api/tv-remote/text/route.ts index 97525da..4b0b02d 100644 --- a/src/app/api/tv-remote/text/route.ts +++ b/src/app/api/tv-remote/text/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { isTVModeEnabled } from '@/lib/tv-mode'; import type { TVRemoteTextCommand } from '@/lib/tv-remote-types'; const { sendTVRemoteCommand } = require('@/lib/tv-remote-hub'); @@ -8,6 +9,10 @@ const { sendTVRemoteCommand } = require('@/lib/tv-remote-hub'); export const runtime = 'nodejs'; export async function POST(request: NextRequest) { + if (!isTVModeEnabled()) { + return NextResponse.json({ error: 'TV 模式未启用' }, { status: 404 }); + } + const authInfo = getAuthInfoFromCookie(request); if (!authInfo?.username) { return NextResponse.json({ error: '未登录' }, { status: 401 }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7b4a74f..2e6d1b4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -255,6 +255,7 @@ export default async function RootLayout({ BANGUMI_DATA_SOURCE: bangumiDataSource, BANGUMI_API_BASE_URL: bangumiApiBaseUrl, BANGUMI_IMAGE_BASE_URL: bangumiImageBaseUrl, + ENABLE_TV_MODE: process.env.ENABLE_TV_MODE !== 'false', ENABLE_TVBOX_SUBSCRIBE: process.env.ENABLE_TVBOX_SUBSCRIBE === 'true', ENABLE_OFFLINE_DOWNLOAD: process.env.NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD === 'true', diff --git a/src/app/tv/layout.tsx b/src/app/tv/layout.tsx index f362818..4b79496 100644 --- a/src/app/tv/layout.tsx +++ b/src/app/tv/layout.tsx @@ -1,12 +1,18 @@ import { ReactNode } from 'react'; +import { notFound } from 'next/navigation'; import TVRemoteReceiver from '@/components/tv/TVRemoteReceiver'; +import { isTVModeEnabled } from '@/lib/tv-mode'; export const metadata = { title: 'TV - MoonTV Plus', }; export default function Layout({ children }: { children: ReactNode }) { + if (!isTVModeEnabled()) { + notFound(); + } + return ( <> {children} diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 2fd53d1..dcab099 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -87,6 +87,7 @@ export const UserMenu: React.FC = () => { // 订阅相关状态 const [subscribeEnabled, setSubscribeEnabled] = useState(false); + const [tvModeEnabled, setTvModeEnabled] = useState(true); const [subscribeUrl, setSubscribeUrl] = useState(''); const [copySuccess, setCopySuccess] = useState(false); const [orionBaseUrlCopySuccess, setOrionBaseUrlCopySuccess] = useState(false); @@ -453,6 +454,7 @@ export const UserMenu: React.FC = () => { const enabled = (window as any).RUNTIME_CONFIG?.ENABLE_TVBOX_SUBSCRIBE || false; setSubscribeEnabled(enabled); + setTvModeEnabled((window as any).RUNTIME_CONFIG?.ENABLE_TV_MODE !== false); } }, []); @@ -4295,14 +4297,22 @@ export const UserMenu: React.FC = () => {
-- 电视端打开 /tv 后会显示二维码;手机在这里打开相机扫描并确认登录。 +
+ {tvModeEnabled + ? '电视端打开 /tv 后会显示二维码;手机在这里打开相机扫描并确认登录。' + : '当前部署未开启 TV 模式,/tv 页面和 Web 电视遥控不可用。'}
+ {!tvModeEnabled && ( +