diff --git a/README.md b/README.md index 165e3b4..4d33cfd 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ - **播放器联动**:暂停时弹幕冻结,跳转时自动清除,全屏模式下正常显示 - **多 API 管理**:每个用户可添加多个弹幕 API,选择当前使用的 API - **优先级规则**:用户选择的弹幕 API 优先于系统默认配置 -- **环境变量预设**:可通过 `NEXT_PUBLIC_DANMAKU_API_URL` 为所有用户预设弹幕 API 地址 +- **环境变量预设**:可通过 `DANMAKU_API_URL` 或 `NEXT_PUBLIC_DANMAKU_API_URL` 为所有用户预设弹幕 API 地址 ### 广告过滤 @@ -456,7 +456,7 @@ docker run -d -p 3000:3000 \ | 变量名 | 说明 | 默认值 | |--------|------|--------| -| `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - | +| `DANMAKU_API_URL` / `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - | 需要自建或使用兼容 [danmu_api](https://github.com/huangxd-/danmu_api) 格式的弹幕聚合服务。 @@ -611,7 +611,7 @@ docker run -e PORT=8080 -p 8080:8080 --name kvideo kuekhaoyang/kvideo:latest | `MERGE_SOURCES` / `NEXT_PUBLIC_MERGE_SOURCES` | 启用合并同名源显示(`true`/`1`) | - | | `AD_KEYWORDS` / `NEXT_PUBLIC_AD_KEYWORDS` | 广告过滤关键词 | - | | `AD_KEYWORDS_FILE` | 广告关键词文件路径 | - | -| `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - | +| `DANMAKU_API_URL` / `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - | | `UPSTASH_REDIS_REST_URL` | Upstash Redis REST URL(跨设备同步:配置、历史、收藏) | - | | `UPSTASH_REDIS_REST_TOKEN` | Upstash Redis REST Token | - | diff --git a/app/api/auth/route.ts b/app/api/auth/route.ts index eae7d08..c9f7c5c 100644 --- a/app/api/auth/route.ts +++ b/app/api/auth/route.ts @@ -15,6 +15,7 @@ const PERSIST_SESSION = process.env.PERSIST_SESSION !== 'false'; // default true const SUBSCRIPTION_SOURCES = process.env.SUBSCRIPTION_SOURCES || process.env.NEXT_PUBLIC_SUBSCRIPTION_SOURCES || ''; const IPTV_SOURCES = process.env.IPTV_SOURCES || process.env.NEXT_PUBLIC_IPTV_SOURCES || ''; const MERGE_SOURCES = process.env.MERGE_SOURCES || process.env.NEXT_PUBLIC_MERGE_SOURCES || ''; +const DANMAKU_API_URL = process.env.DANMAKU_API_URL || process.env.NEXT_PUBLIC_DANMAKU_API_URL || ''; // Backward compat: ACCESS_PASSWORD acts as ADMIN_PASSWORD if ADMIN_PASSWORD is not set const effectiveAdminPassword = ADMIN_PASSWORD || ACCESS_PASSWORD; @@ -73,6 +74,7 @@ export async function GET() { subscriptionSources: SUBSCRIPTION_SOURCES, iptvSources: IPTV_SOURCES, mergeSources: MERGE_SOURCES, + danmakuApiUrl: DANMAKU_API_URL, }); } diff --git a/components/PasswordGate.tsx b/components/PasswordGate.tsx index ae62ad9..e5dcf84 100644 --- a/components/PasswordGate.tsx +++ b/components/PasswordGate.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import { getSession, setSession } from '@/lib/store/auth-store'; import { useSubscriptionSync } from '@/lib/hooks/useSubscriptionSync'; -import { settingsStore } from '@/lib/store/settings-store'; +import { hasStoredAppSetting, settingsStore } from '@/lib/store/settings-store'; import { useIPTVStore } from '@/lib/store/iptv-store'; import { Lock } from 'lucide-react'; @@ -53,6 +53,18 @@ function syncMergeSources(rawValue: string) { } } +function syncDanmakuApiUrl(rawValue: string) { + if (!rawValue || hasStoredAppSetting('danmakuApiUrl')) return; + + const settings = settingsStore.getSettings(); + if (settings.danmakuApiUrl !== rawValue) { + settingsStore.saveSettings({ + ...settings, + danmakuApiUrl: rawValue, + }); + } +} + export function PasswordGate({ children, hasAuth: initialHasAuth }: { children: React.ReactNode, hasAuth: boolean }) { // Enable background subscription syncing globally useSubscriptionSync(); @@ -106,6 +118,10 @@ export function PasswordGate({ children, hasAuth: initialHasAuth }: { children: syncMergeSources(data.mergeSources); } + if (data.danmakuApiUrl) { + syncDanmakuApiUrl(data.danmakuApiUrl); + } + // Re-evaluate lock status with confirmed server state const confirmLocked = data.hasAuth && !isAuthenticated; setIsLocked(confirmLocked); diff --git a/lib/store/settings-store.ts b/lib/store/settings-store.ts index 3c5a9b0..c6f24c7 100644 --- a/lib/store/settings-store.ts +++ b/lib/store/settings-store.ts @@ -139,6 +139,24 @@ function getDefaultAppSettings(): AppSettings { }; } +export function hasStoredAppSetting(key: keyof AppSettings): boolean { + if (typeof window === 'undefined') { + return false; + } + + const stored = localStorage.getItem(SETTINGS_KEY); + if (!stored) { + return false; + } + + try { + const parsed = JSON.parse(stored); + return Object.prototype.hasOwnProperty.call(parsed, key); + } catch { + return false; + } +} + export const settingsStore = { getSettings(): AppSettings { // SSR: Return defaults