/* eslint-disable no-console,@typescript-eslint/no-explicit-any */ import { NextRequest, NextResponse } from 'next/server'; import { getConfig, refineConfig } from '@/lib/config'; import { db, getStorage } from '@/lib/db'; import { fetchVideoDetail } from '@/lib/fetchVideoDetail'; import { refreshLiveChannels } from '@/lib/live'; import { startOpenListRefresh } from '@/lib/openlist-refresh'; import { SearchResult } from '@/lib/types'; export const runtime = 'nodejs'; export async function GET( request: NextRequest, { params }: { params: { password: string } } ) { console.log(request.url); const cronPassword = process.env.CRON_PASSWORD || 'mtvpls'; if (params.password !== cronPassword) { return NextResponse.json( { success: false, message: 'Unauthorized' }, { status: 401 } ); } try { console.log('Cron job triggered:', new Date().toISOString()); cronJob(); return NextResponse.json({ success: true, message: 'Cron job executed successfully', timestamp: new Date().toISOString(), }); } catch (error) { console.error('Cron job failed:', error); return NextResponse.json( { success: false, message: 'Cron job failed', error: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date().toISOString(), }, { status: 500 } ); } } async function cronJob() { await refreshConfig(); await refreshAllLiveChannels(); await refreshOpenList(); await refreshRecordAndFavorites(); } async function refreshAllLiveChannels() { const config = await getConfig(); // 并发刷新所有启用的直播源 const refreshPromises = (config.LiveConfig || []) .filter(liveInfo => !liveInfo.disabled) .map(async (liveInfo) => { try { const nums = await refreshLiveChannels(liveInfo); liveInfo.channelNumber = nums; } catch (error) { console.error(`刷新直播源失败 [${liveInfo.name || liveInfo.key}]:`, error); liveInfo.channelNumber = 0; } }); // 等待所有刷新任务完成 await Promise.all(refreshPromises); // 保存配置 await db.saveAdminConfig(config); } async function refreshConfig() { let config = await getConfig(); if (config && config.ConfigSubscribtion && config.ConfigSubscribtion.URL && config.ConfigSubscribtion.AutoUpdate) { try { const response = await fetch(config.ConfigSubscribtion.URL); if (!response.ok) { throw new Error(`请求失败: ${response.status} ${response.statusText}`); } const configContent = await response.text(); // 对 configContent 进行 base58 解码 let decodedContent; try { const bs58 = (await import('bs58')).default; const decodedBytes = bs58.decode(configContent); decodedContent = new TextDecoder().decode(decodedBytes); } catch (decodeError) { console.warn('Base58 解码失败:', decodeError); throw decodeError; } try { JSON.parse(decodedContent); } catch (e) { throw new Error('配置文件格式错误,请检查 JSON 语法'); } config.ConfigFile = decodedContent; config.ConfigSubscribtion.LastCheck = new Date().toISOString(); config = refineConfig(config); await db.saveAdminConfig(config); // 清除短剧视频源缓存(因为配置文件可能包含新的视频源) try { await db.deleteGlobalValue('duanju'); console.log('已清除短剧视频源缓存'); } catch (error) { console.error('清除短剧视频源缓存失败:', error); // 不影响主流程,继续执行 } } catch (e) { console.error('刷新配置失败:', e); } } else { console.log('跳过刷新:未配置订阅地址或自动更新'); } } async function refreshRecordAndFavorites() { try { const users = await db.getAllUsers(); if (process.env.USERNAME && !users.includes(process.env.USERNAME)) { users.push(process.env.USERNAME); } // 函数级缓存:key 为 `${source}+${id}`,值为 Promise const detailCache = new Map>(); // 获取详情 Promise(带缓存和错误处理) const getDetail = async ( source: string, id: string, fallbackTitle: string ): Promise => { const key = `${source}+${id}`; let promise = detailCache.get(key); if (!promise) { promise = fetchVideoDetail({ source, id, fallbackTitle: fallbackTitle.trim(), }) .then((detail) => { // 成功时才缓存结果 const successPromise = Promise.resolve(detail); detailCache.set(key, successPromise); return detail; }) .catch((err) => { console.error(`获取视频详情失败 (${source}+${id}):`, err); return null; }); } return promise; }; for (const user of users) { console.log(`开始处理用户: ${user}`); const storage = getStorage(); // 播放记录 try { const playRecords = await db.getAllPlayRecords(user); const totalRecords = Object.keys(playRecords).length; let processedRecords = 0; for (const [key, record] of Object.entries(playRecords)) { try { const [source, id] = key.split('+'); if (!source || !id) { console.warn(`跳过无效的播放记录键: ${key}`); continue; } const detail = await getDetail(source, id, record.title); if (!detail) { console.warn(`跳过无法获取详情的播放记录: ${key}`); continue; } const episodeCount = detail.episodes?.length || 0; if (episodeCount > 0 && episodeCount !== record.total_episodes) { await db.savePlayRecord(user, source, id, { title: detail.title || record.title, source_name: record.source_name, cover: detail.poster || record.cover, index: record.index, total_episodes: episodeCount, play_time: record.play_time, year: detail.year || record.year, total_time: record.total_time, save_time: record.save_time, search_title: record.search_title, }); console.log( `更新播放记录: ${record.title} (${record.total_episodes} -> ${episodeCount})` ); } processedRecords++; } catch (err) { console.error(`处理播放记录失败 (${key}):`, err); // 继续处理下一个记录 } } console.log(`播放记录处理完成: ${processedRecords}/${totalRecords}`); } catch (err) { console.error(`获取用户播放记录失败 (${user}):`, err); } // 收藏 try { let favorites = await db.getAllFavorites(user); favorites = Object.fromEntries( Object.entries(favorites).filter(([_, fav]) => fav.origin !== 'live') ); const totalFavorites = Object.keys(favorites).length; let processedFavorites = 0; const now = Date.now(); for (const [key, fav] of Object.entries(favorites)) { try { const [source, id] = key.split('+'); if (!source || !id) { console.warn(`跳过无效的收藏键: ${key}`); continue; } const favDetail = await getDetail(source, id, fav.title); if (!favDetail) { console.warn(`跳过无法获取详情的收藏: ${key}`); continue; } const favEpisodeCount = favDetail.episodes?.length || 0; if (favEpisodeCount > 0 && favEpisodeCount !== fav.total_episodes) { await db.saveFavorite(user, source, id, { title: favDetail.title || fav.title, source_name: fav.source_name, cover: favDetail.poster || fav.cover, year: favDetail.year || fav.year, total_episodes: favEpisodeCount, save_time: fav.save_time, search_title: fav.search_title, }); console.log( `更新收藏: ${fav.title} (${fav.total_episodes} -> ${favEpisodeCount})` ); // 创建通知 const notification = { id: `fav_update_${source}_${id}_${now}`, type: 'favorite_update' as const, title: '收藏更新', message: `《${fav.title}》有新集数更新!从 ${fav.total_episodes} 集更新到 ${favEpisodeCount} 集`, timestamp: now, read: false, metadata: { source, id, title: fav.title, old_episodes: fav.total_episodes, new_episodes: favEpisodeCount, }, }; await storage.addNotification(user, notification); console.log(`已为用户 ${user} 创建收藏更新通知: ${fav.title}`); } processedFavorites++; } catch (err) { console.error(`处理收藏失败 (${key}):`, err); // 继续处理下一个收藏 } } console.log(`收藏处理完成: ${processedFavorites}/${totalFavorites}`); } catch (err) { console.error(`获取用户收藏失败 (${user}):`, err); } } console.log('刷新播放记录/收藏任务完成'); } catch (err) { console.error('刷新播放记录/收藏任务启动失败', err); } } async function refreshOpenList() { try { const config = await getConfig(); const openListConfig = config.OpenListConfig; // 检查功能是否启用 if (!openListConfig || !openListConfig.Enabled) { console.log('跳过 OpenList 扫描:功能未启用'); return; } // 检查是否配置了 OpenList 和定时扫描 if (!openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { console.log('跳过 OpenList 扫描:未配置'); return; } const scanInterval = openListConfig.ScanInterval || 0; if (scanInterval === 0) { console.log('跳过 OpenList 扫描:定时扫描已关闭'); return; } // 检查间隔时间是否满足最低要求(60分钟) if (scanInterval < 60) { console.log(`跳过 OpenList 扫描:间隔时间 ${scanInterval} 分钟小于最低要求 60 分钟`); return; } // 检查上次扫描时间 const lastRefreshTime = openListConfig.LastRefreshTime || 0; const now = Date.now(); const timeSinceLastRefresh = now - lastRefreshTime; const intervalMs = scanInterval * 60 * 1000; if (timeSinceLastRefresh < intervalMs) { const remainingMinutes = Math.ceil((intervalMs - timeSinceLastRefresh) / 60000); console.log(`跳过 OpenList 扫描:距离上次扫描仅 ${Math.floor(timeSinceLastRefresh / 60000)} 分钟,还需等待 ${remainingMinutes} 分钟`); return; } console.log(`开始 OpenList 定时扫描(间隔: ${scanInterval} 分钟)`); // 直接调用扫描函数(立即扫描模式,不清空 metainfo) const { taskId } = await startOpenListRefresh(false); console.log('OpenList 定时扫描已启动,任务ID:', taskId); } catch (err) { console.error('OpenList 定时扫描失败:', err); } }