增加电视直播刷新间隔配置

This commit is contained in:
mtvpls
2026-05-06 09:57:31 +08:00
parent 6d9e092448
commit cb47150eb2
5 changed files with 119 additions and 7 deletions
+64 -5
View File
@@ -13372,6 +13372,7 @@ const LiveSourceConfig = ({
useState<LiveDataSource | null>(null);
const [orderChanged, setOrderChanged] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [refreshIntervalHours, setRefreshIntervalHours] = useState(12);
const [newLiveSource, setNewLiveSource] = useState<LiveDataSource>({
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 (
<div className='space-y-6'>
{/* 添加直播源表单 */}
<div className='flex items-center justify-between'>
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
</h4>
<div className='flex items-center space-x-2'>
<div className='space-y-4'>
<div className='flex items-end justify-between gap-3'>
<div className='flex items-end gap-2 flex-nowrap'>
<div className='min-w-0'>
<label className='block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1 whitespace-nowrap'>
</label>
<input
type='number'
min='1'
value={refreshIntervalHours}
onChange={(e) => 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'
/>
</div>
<button
onClick={handleSaveRefreshInterval}
disabled={isLoading('saveLiveRefreshInterval')}
className={`px-3 py-1.5 text-sm whitespace-nowrap shrink-0 ${
isLoading('saveLiveRefreshInterval') ? buttonStyles.disabled : buttonStyles.success
}`}
>
{isLoading('saveLiveRefreshInterval') ? '保存中...' : '保存间隔'}
</button>
</div>
</div>
<div className='flex items-center justify-between'>
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
</h4>
<div className='flex items-center space-x-2'>
<button
onClick={handleRefreshLiveSources}
disabled={isRefreshing || isLoading('refreshLiveSources')}
@@ -13766,6 +13824,7 @@ const LiveSourceConfig = ({
>
{showAddForm ? '取消' : '添加直播源'}
</button>
</div>
</div>
</div>
+20 -2
View File
@@ -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);
}
+1
View File
@@ -97,6 +97,7 @@ export interface AdminConfig {
from: 'config' | 'custom';
disabled?: boolean;
}[];
LiveRefreshIntervalHours?: number; // 电视直播全局刷新间隔(小时),默认12小时
LiveConfig?: {
key: string;
name: string;
+12
View File
@@ -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);
// 用户信息已迁移到新版数据库
// 这里只保留站长用户用于兼容性,其他用户从数据库读取
+22
View File
@@ -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: {