增加tv mode开关

This commit is contained in:
mtvpls
2026-06-01 02:05:05 +08:00
parent 3099cf9bfa
commit a682998930
11 changed files with 83 additions and 15 deletions
+2
View File
@@ -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,
+5
View File
@@ -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 });
+5
View File
@@ -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 });
+5
View File
@@ -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 });
+1
View File
@@ -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',
+6
View File
@@ -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}
+16 -4
View File
@@ -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 = () => {
</p>
</div>
</div>
<p className='mt-5 text-sm leading-6 text-slate-600 dark:text-slate-400'>
/tv
<p className='mt-5 text-sm leading-6 text-slate-600 dark:text-slate-400'>
{tvModeEnabled
? '电视端打开 /tv 后会显示二维码;手机在这里打开相机扫描并确认登录。'
: '当前部署未开启 TV 模式,/tv 页面和 Web 电视遥控不可用。'}
</p>
{!tvModeEnabled && (
<div className='mt-5 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-bold text-amber-800 dark:border-amber-300/20 dark:bg-amber-400/10 dark:text-amber-200'>
TV ENABLE_TV_MODE=true
</div>
)}
<div className='mt-5 grid gap-2 sm:grid-cols-2'>
<button
type='button'
onClick={startTvQrScanner}
className='inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-rose-600 px-4 py-3 text-sm font-black text-white transition hover:bg-rose-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500/70'
disabled={!tvModeEnabled}
className='inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-rose-600 px-4 py-3 text-sm font-black text-white transition hover:bg-rose-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500/70 disabled:cursor-not-allowed disabled:bg-slate-300 disabled:text-slate-500 dark:disabled:bg-white/10 dark:disabled:text-slate-500'
>
<Smartphone className='h-4 w-4' />
@@ -4310,10 +4320,12 @@ export const UserMenu: React.FC = () => {
<button
type='button'
onClick={() => {
if (!tvModeEnabled) return;
setIsSubscribeOpen(false);
setIsTVRemoteOpen(true);
}}
className='inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-slate-950 px-4 py-3 text-sm font-black text-white transition hover:bg-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500/70 dark:bg-white dark:text-slate-950 dark:hover:bg-slate-200'
disabled={!tvModeEnabled}
className='inline-flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-slate-950 px-4 py-3 text-sm font-black text-white transition hover:bg-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500/70 disabled:cursor-not-allowed disabled:bg-slate-300 disabled:text-slate-500 dark:bg-white dark:text-slate-950 dark:hover:bg-slate-200 dark:disabled:bg-white/10 dark:disabled:text-slate-500'
>
<Sliders className='h-4 w-4' />
+3
View File
@@ -0,0 +1,3 @@
export function isTVModeEnabled() {
return process.env.ENABLE_TV_MODE !== 'false';
}
+9
View File
@@ -4,10 +4,15 @@ import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { TOKEN_CONFIG } from '@/lib/refresh-token';
import { isTVModeEnabled } from '@/lib/tv-mode';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (!isTVModeEnabled() && isTVModePath(pathname)) {
return new NextResponse('Not Found', { status: 404 });
}
// 跳过不需要认证的路径
if (shouldSkipAuth(pathname)) {
return NextResponse.next();
@@ -171,6 +176,10 @@ function shouldSkipAuth(pathname: string): boolean {
return skipPaths.some((path) => pathname.startsWith(path));
}
function isTVModePath(pathname: string): boolean {
return pathname === '/tv' || pathname.startsWith('/tv/') || pathname.startsWith('/api/tv-remote/');
}
// 配置middleware匹配规则
export const config = {
matcher: [