diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index e7a8217..31134b0 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -13372,6 +13372,7 @@ const LiveSourceConfig = ({ useState(null); const [orderChanged, setOrderChanged] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); + const [refreshIntervalHours, setRefreshIntervalHours] = useState(12); const [newLiveSource, setNewLiveSource] = useState({ name: '', key: '', @@ -13401,6 +13402,7 @@ const LiveSourceConfig = ({ useEffect(() => { if (config?.LiveConfig) { setLiveSources(config.LiveConfig); + setRefreshIntervalHours(config.LiveRefreshIntervalHours || 12); // 进入时重置 orderChanged setOrderChanged(false); } @@ -13495,6 +13497,36 @@ const LiveSourceConfig = ({ }; // 刷新直播源 + const handleSaveRefreshInterval = () => { + withLoading('saveLiveRefreshInterval', async () => { + if (!config) return; + + const response = await fetch('/api/admin/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ...config, + LiveRefreshIntervalHours: Math.max(1, refreshIntervalHours || 12), + }), + }); + + if (!response.ok) { + const data = await response.json().catch(() => ({})); + throw new Error(data.error || `保存失败: ${response.status}`); + } + + await refreshConfig(); + showAlert({ + type: 'success', + title: '保存成功', + message: '电视直播刷新间隔已保存', + timer: 2000, + }); + }).catch((err) => { + showError(err instanceof Error ? err.message : '保存失败', showAlert); + }); + }; + const handleRefreshLiveSources = async () => { if (isRefreshing) return; @@ -13738,11 +13770,37 @@ const LiveSourceConfig = ({ return (
{/* 添加直播源表单 */} -
-

- 直播源列表 -

-
+
+
+
+
+ + setRefreshIntervalHours(Math.max(1, parseInt(e.target.value) || 12))} + className='px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 w-28 sm:w-40' + /> +
+ +
+
+
+

+ 直播源列表 +

+
+
diff --git a/src/app/api/cron/[password]/route.ts b/src/app/api/cron/[password]/route.ts index 5fe463f..1017f5f 100644 --- a/src/app/api/cron/[password]/route.ts +++ b/src/app/api/cron/[password]/route.ts @@ -8,12 +8,17 @@ import { db, getStorage } from '@/lib/db'; import { EmailService } from '@/lib/email.service'; import { FavoriteUpdate, - MangaShelfUpdate, getBatchFavoriteUpdateEmailTemplate, getBatchMangaUpdateEmailTemplate, + MangaShelfUpdate, } from '@/lib/email.templates'; import { fetchVideoDetail } from '@/lib/fetchVideoDetail'; -import { refreshLiveChannels } from '@/lib/live'; +import { + getLastGlobalLiveRefreshTime, + getLiveRefreshIntervalHours, + refreshLiveChannels, + setLastGlobalLiveRefreshTime, +} from '@/lib/live'; import { MangaChapter, MangaShelfItem } from '@/lib/manga.types'; import { startOpenListRefresh } from '@/lib/openlist-refresh'; import { getSuwayomiConfig, loginWithSimpleAuth, SuwayomiClient } from '@/lib/suwayomi.client'; @@ -236,6 +241,17 @@ async function cronJob() { async function refreshAllLiveChannels() { const config = await getConfig(); + const refreshIntervalHours = getLiveRefreshIntervalHours(config.LiveRefreshIntervalHours); + const lastRefreshTime = getLastGlobalLiveRefreshTime(); + const now = Date.now(); + const intervalMs = refreshIntervalHours * 60 * 60 * 1000; + const timeSinceLastRefresh = now - lastRefreshTime; + + if (lastRefreshTime > 0 && timeSinceLastRefresh < intervalMs) { + const remainingHours = Math.ceil((intervalMs - timeSinceLastRefresh) / (60 * 60 * 1000)); + console.log(`跳过刷新电视直播:距离上次刷新仅 ${Math.floor(timeSinceLastRefresh / (60 * 60 * 1000))} 小时,还需等待 ${remainingHours} 小时`); + return; + } // 并发刷新所有启用的直播源 const refreshPromises = (config.LiveConfig || []) @@ -253,6 +269,8 @@ async function refreshAllLiveChannels() { // 等待所有刷新任务完成 await Promise.all(refreshPromises); + setLastGlobalLiveRefreshTime(Date.now()); + // 保存配置 await db.saveAdminConfig(config); } diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index 9c5992e..f41b7b4 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -97,6 +97,7 @@ export interface AdminConfig { from: 'config' | 'custom'; disabled?: boolean; }[]; + LiveRefreshIntervalHours?: number; // 电视直播全局刷新间隔(小时),默认12小时 LiveConfig?: { key: string; name: string; diff --git a/src/lib/config.ts b/src/lib/config.ts index a423224..49d0b25 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -5,6 +5,17 @@ import { db } from '@/lib/db'; import { AdminConfig } from './admin.types'; const BUILTIN_DANMAKU_API_BASE = 'https://mtvpls-danmu.netlify.app/87654321'; +const DEFAULT_LIVE_REFRESH_INTERVAL_HOURS = 12; + +function normalizeLiveRefreshIntervalHours(refreshIntervalHours?: number): number { + const normalizedInterval = Number(refreshIntervalHours); + + if (!Number.isFinite(normalizedInterval) || normalizedInterval <= 0) { + return DEFAULT_LIVE_REFRESH_INTERVAL_HOURS; + } + + return Math.floor(normalizedInterval); +} export interface ApiSite { key: string; @@ -545,6 +556,7 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { if (!adminConfig.LiveConfig || !Array.isArray(adminConfig.LiveConfig)) { adminConfig.LiveConfig = []; } + adminConfig.LiveRefreshIntervalHours = normalizeLiveRefreshIntervalHours(adminConfig.LiveRefreshIntervalHours); // 用户信息已迁移到新版数据库 // 这里只保留站长用户用于兼容性,其他用户从数据库读取 diff --git a/src/lib/live.ts b/src/lib/live.ts index f3f284c..d28e8df 100644 --- a/src/lib/live.ts +++ b/src/lib/live.ts @@ -5,6 +5,28 @@ import { db } from '@/lib/db'; const defaultUA = 'AptvPlayer/1.4.10'; +export const DEFAULT_LIVE_REFRESH_INTERVAL_HOURS = 12; + +let lastGlobalLiveRefreshTime = 0; + +export function getLiveRefreshIntervalHours(refreshIntervalHours?: number): number { + const normalizedInterval = Number(refreshIntervalHours); + + if (!Number.isFinite(normalizedInterval) || normalizedInterval <= 0) { + return DEFAULT_LIVE_REFRESH_INTERVAL_HOURS; + } + + return Math.floor(normalizedInterval); +} + +export function getLastGlobalLiveRefreshTime(): number { + return lastGlobalLiveRefreshTime; +} + +export function setLastGlobalLiveRefreshTime(timestamp: number): void { + lastGlobalLiveRefreshTime = timestamp; +} + export interface LiveChannels { channelNumber: number; channels: {