增加电视直播刷新间隔配置
This commit is contained in:
@@ -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,6 +13770,32 @@ const LiveSourceConfig = ({
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
{/* 添加直播源表单 */}
|
||||
<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'>
|
||||
直播源列表
|
||||
@@ -13768,6 +13826,7 @@ const LiveSourceConfig = ({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAddForm && (
|
||||
<div className='p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 space-y-4'>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ export interface AdminConfig {
|
||||
from: 'config' | 'custom';
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
LiveRefreshIntervalHours?: number; // 电视直播全局刷新间隔(小时),默认12小时
|
||||
LiveConfig?: {
|
||||
key: string;
|
||||
name: string;
|
||||
|
||||
@@ -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);
|
||||
|
||||
// 用户信息已迁移到新版数据库
|
||||
// 这里只保留站长用户用于兼容性,其他用户从数据库读取
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user