import { getLocalMovies, saveLocalMovie, syncFromMovies } from '../../../lib/localDb.js'; // Cấu hình timeout cho fetch async function fetchWithTimeout(resource, options = {}) { const { timeout = 8000 } = options; const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(resource, { ...options, signal: controller.signal }); return response; } finally { clearTimeout(id); } } // Định dạng tên tập phim function formatEpisodeName(name) { if (!name) return name; let str = name.trim(); if (/^\d+$/.test(str)) { const num = parseInt(str, 10); return `Tập ${num.toString().padStart(2, '0')}`; } const match = str.match(/^(tập|tap)\s+(\d+)$/i); if (match) { const num = parseInt(match[2], 10); return `Tập ${num.toString().padStart(2, '0')}`; } return str; } // Lọc server phát phim function cleanAndFilterServers(episodes) { if (!Array.isArray(episodes)) return []; const tempMap = new Map(); for (const srv of episodes) { if (!srv || !srv.server_name) continue; let rawName = srv.server_name.trim(); let cleanName = ''; if (/vietsub/i.test(rawName)) { cleanName = 'Vietsub'; } else if (/thuyết minh|thuyet minh/i.test(rawName)) { cleanName = 'Thuyết Minh'; } else if (/lồng tiếng|long tieng/i.test(rawName)) { cleanName = 'Lồng Tiếng'; } else { cleanName = rawName.replace(/#\s*/g, '').replace(/\([^)]*\)/g, '').trim(); if (!cleanName) cleanName = 'Vietsub'; } if (cleanName !== 'Vietsub' && cleanName !== 'Thuyết Minh' && cleanName !== 'Lồng Tiếng') { continue; } if (!tempMap.has(cleanName)) { srv.server_name = cleanName; tempMap.set(cleanName, srv); } } const result = []; if (tempMap.has('Vietsub')) result.push(tempMap.get('Vietsub')); if (tempMap.has('Thuyết Minh')) result.push(tempMap.get('Thuyết Minh')); if (result.length < 2 && tempMap.has('Lồng Tiếng')) { result.push(tempMap.get('Lồng Tiếng')); } if (result.length === 0 && episodes.length > 0) { const fallbackSrv = episodes[0]; fallbackSrv.server_name = fallbackSrv.server_name.replace(/#\s*/g, '').replace(/\([^)]*\)/g, '').trim() || 'Vietsub'; result.push(fallbackSrv); } return result; } // Gộp tập phim function mergeEpisodes(existingServerList, crawledServerList) { let addedCount = 0; const existingList = JSON.parse(JSON.stringify(existingServerList || [])); for (const crawledServer of crawledServerList) { let existingServer = existingList.find(s => s.server_name === crawledServer.server_name); if (!existingServer) { crawledServer.server_data.forEach(ep => { ep.name = formatEpisodeName(ep.name); }); existingList.push(crawledServer); addedCount += crawledServer.server_data.length; continue; } existingServer.server_data.forEach(ep => { ep.name = formatEpisodeName(ep.name); }); const existingMap = new Map(existingServer.server_data.map((e, idx) => [e.name, { ep: e, idx }])); for (const crawledEp of crawledServer.server_data) { crawledEp.name = formatEpisodeName(crawledEp.name); const matched = existingMap.get(crawledEp.name); if (matched) { existingServer.server_data[matched.idx] = { ...crawledEp, introStart: matched.ep.introStart, introEnd: matched.ep.introEnd, outroStart: matched.ep.outroStart, outroEnd: matched.ep.outroEnd }; } else { existingServer.server_data.push(crawledEp); addedCount++; } } } return { merged: existingList, addedCount }; } export async function POST({ request }) { // Check quyền admin từ cookie auth_token const cookies = request.headers.get('cookie') || ''; const hasToken = cookies.includes('auth_token='); if (!hasToken) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } try { const { slug, selectedEpisodes } = await request.json(); if (!slug) { return new Response(JSON.stringify({ error: 'Missing slug parameter' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const localMovies = await getLocalMovies(); const res = await fetchWithTimeout(`https://phimapi.com/phim/${slug}`); if (!res.ok) { throw new Error(`Phim không tồn tại trên KKPhim (HTTP ${res.status})`); } const data = await res.json(); if (!data.status || !data.movie) { throw new Error('Dữ liệu API KKPhim bị rỗng hoặc không đúng định dạng'); } const crawledMovie = data.movie; const crawledEpisodes = cleanAndFilterServers(data.episodes || []); // Lọc tập phim nếu admin chọn cụ thể if (Array.isArray(selectedEpisodes) && selectedEpisodes.length > 0) { crawledEpisodes.forEach(server => { if (server.server_data) { server.server_data = server.server_data.filter(ep => { const formattedName = formatEpisodeName(ep.name); return selectedEpisodes.includes(formattedName); }); } }); } const existing = localMovies[slug]; let status = 'success'; let addedCount = 0; let message = ''; if (existing) { // So sánh gộp tập phim const { merged, addedCount: added } = mergeEpisodes(existing.episodes, crawledEpisodes); addedCount = added; const hasSelectedEpisodes = Array.isArray(selectedEpisodes) && selectedEpisodes.length > 0; if (addedCount > 0 || hasSelectedEpisodes) { existing.movie = { ...existing.movie, ...crawledMovie, episode_current: crawledMovie.episode_current || existing.movie.episode_current, episode_total: crawledMovie.episode_total || existing.movie.episode_total || null, modified: { time: new Date().toISOString() } }; existing.episodes = merged; await saveLocalMovie(slug, existing); status = 'updated'; message = hasSelectedEpisodes && addedCount === 0 ? `Thành công (Cập nhật liên kết ${selectedEpisodes.length} tập)` : `Thành công (Cập nhật +${addedCount} tập mới)`; } else { status = 'skipped'; message = 'Bỏ qua (Đã đầy đủ tập, không có tập mới)'; } } else { // Phim mới hoàn toàn crawledEpisodes.forEach(srv => { if (srv.server_data) { srv.server_data.forEach(ep => { ep.name = formatEpisodeName(ep.name); }); } }); const newMovie = { movie: crawledMovie, episodes: crawledEpisodes }; await saveLocalMovie(slug, newMovie); status = 'success'; message = 'Thành công (Phim mới)'; } // Tự động đồng bộ các danh mục Thể loại, Quốc gia, Diễn viên và Server ngay lập tức! await syncFromMovies('genres'); await syncFromMovies('countries'); await syncFromMovies('actors'); await syncFromMovies('servers'); return new Response(JSON.stringify({ success: true, status, slug, name: crawledMovie.name, added: addedCount, message }), { headers: { 'Content-Type': 'application/json' } }); } catch (err) { return new Response(JSON.stringify({ success: false, error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }