增加电视直播刷新间隔配置
This commit is contained in:
+64
-5
@@ -13372,6 +13372,7 @@ const LiveSourceConfig = ({
|
|||||||
useState<LiveDataSource | null>(null);
|
useState<LiveDataSource | null>(null);
|
||||||
const [orderChanged, setOrderChanged] = useState(false);
|
const [orderChanged, setOrderChanged] = useState(false);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
const [refreshIntervalHours, setRefreshIntervalHours] = useState(12);
|
||||||
const [newLiveSource, setNewLiveSource] = useState<LiveDataSource>({
|
const [newLiveSource, setNewLiveSource] = useState<LiveDataSource>({
|
||||||
name: '',
|
name: '',
|
||||||
key: '',
|
key: '',
|
||||||
@@ -13401,6 +13402,7 @@ const LiveSourceConfig = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config?.LiveConfig) {
|
if (config?.LiveConfig) {
|
||||||
setLiveSources(config.LiveConfig);
|
setLiveSources(config.LiveConfig);
|
||||||
|
setRefreshIntervalHours(config.LiveRefreshIntervalHours || 12);
|
||||||
// 进入时重置 orderChanged
|
// 进入时重置 orderChanged
|
||||||
setOrderChanged(false);
|
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 () => {
|
const handleRefreshLiveSources = async () => {
|
||||||
if (isRefreshing) return;
|
if (isRefreshing) return;
|
||||||
|
|
||||||
@@ -13738,11 +13770,37 @@ const LiveSourceConfig = ({
|
|||||||
return (
|
return (
|
||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
{/* 添加直播源表单 */}
|
{/* 添加直播源表单 */}
|
||||||
<div className='flex items-center justify-between'>
|
<div className='space-y-4'>
|
||||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
<div className='flex items-end justify-between gap-3'>
|
||||||
直播源列表
|
<div className='flex items-end gap-2 flex-nowrap'>
|
||||||
</h4>
|
<div className='min-w-0'>
|
||||||
<div className='flex items-center space-x-2'>
|
<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
|
<button
|
||||||
onClick={handleRefreshLiveSources}
|
onClick={handleRefreshLiveSources}
|
||||||
disabled={isRefreshing || isLoading('refreshLiveSources')}
|
disabled={isRefreshing || isLoading('refreshLiveSources')}
|
||||||
@@ -13766,6 +13824,7 @@ const LiveSourceConfig = ({
|
|||||||
>
|
>
|
||||||
{showAddForm ? '取消' : '添加直播源'}
|
{showAddForm ? '取消' : '添加直播源'}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ import { db, getStorage } from '@/lib/db';
|
|||||||
import { EmailService } from '@/lib/email.service';
|
import { EmailService } from '@/lib/email.service';
|
||||||
import {
|
import {
|
||||||
FavoriteUpdate,
|
FavoriteUpdate,
|
||||||
MangaShelfUpdate,
|
|
||||||
getBatchFavoriteUpdateEmailTemplate,
|
getBatchFavoriteUpdateEmailTemplate,
|
||||||
getBatchMangaUpdateEmailTemplate,
|
getBatchMangaUpdateEmailTemplate,
|
||||||
|
MangaShelfUpdate,
|
||||||
} from '@/lib/email.templates';
|
} from '@/lib/email.templates';
|
||||||
import { fetchVideoDetail } from '@/lib/fetchVideoDetail';
|
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 { MangaChapter, MangaShelfItem } from '@/lib/manga.types';
|
||||||
import { startOpenListRefresh } from '@/lib/openlist-refresh';
|
import { startOpenListRefresh } from '@/lib/openlist-refresh';
|
||||||
import { getSuwayomiConfig, loginWithSimpleAuth, SuwayomiClient } from '@/lib/suwayomi.client';
|
import { getSuwayomiConfig, loginWithSimpleAuth, SuwayomiClient } from '@/lib/suwayomi.client';
|
||||||
@@ -236,6 +241,17 @@ async function cronJob() {
|
|||||||
|
|
||||||
async function refreshAllLiveChannels() {
|
async function refreshAllLiveChannels() {
|
||||||
const config = await getConfig();
|
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 || [])
|
const refreshPromises = (config.LiveConfig || [])
|
||||||
@@ -253,6 +269,8 @@ async function refreshAllLiveChannels() {
|
|||||||
// 等待所有刷新任务完成
|
// 等待所有刷新任务完成
|
||||||
await Promise.all(refreshPromises);
|
await Promise.all(refreshPromises);
|
||||||
|
|
||||||
|
setLastGlobalLiveRefreshTime(Date.now());
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
await db.saveAdminConfig(config);
|
await db.saveAdminConfig(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export interface AdminConfig {
|
|||||||
from: 'config' | 'custom';
|
from: 'config' | 'custom';
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}[];
|
}[];
|
||||||
|
LiveRefreshIntervalHours?: number; // 电视直播全局刷新间隔(小时),默认12小时
|
||||||
LiveConfig?: {
|
LiveConfig?: {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ import { db } from '@/lib/db';
|
|||||||
import { AdminConfig } from './admin.types';
|
import { AdminConfig } from './admin.types';
|
||||||
|
|
||||||
const BUILTIN_DANMAKU_API_BASE = 'https://mtvpls-danmu.netlify.app/87654321';
|
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 {
|
export interface ApiSite {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -545,6 +556,7 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
|
|||||||
if (!adminConfig.LiveConfig || !Array.isArray(adminConfig.LiveConfig)) {
|
if (!adminConfig.LiveConfig || !Array.isArray(adminConfig.LiveConfig)) {
|
||||||
adminConfig.LiveConfig = [];
|
adminConfig.LiveConfig = [];
|
||||||
}
|
}
|
||||||
|
adminConfig.LiveRefreshIntervalHours = normalizeLiveRefreshIntervalHours(adminConfig.LiveRefreshIntervalHours);
|
||||||
|
|
||||||
// 用户信息已迁移到新版数据库
|
// 用户信息已迁移到新版数据库
|
||||||
// 这里只保留站长用户用于兼容性,其他用户从数据库读取
|
// 这里只保留站长用户用于兼容性,其他用户从数据库读取
|
||||||
|
|||||||
@@ -5,6 +5,28 @@ import { db } from '@/lib/db';
|
|||||||
|
|
||||||
const defaultUA = 'AptvPlayer/1.4.10';
|
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 {
|
export interface LiveChannels {
|
||||||
channelNumber: number;
|
channelNumber: number;
|
||||||
channels: {
|
channels: {
|
||||||
|
|||||||
Reference in New Issue
Block a user