import { getLocalMovies } 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); } } // Trích xuất slug từ URL hoặc chuỗi nhập vào function extractSlug(input) { if (!input) return null; const trimmed = input.trim(); if (!trimmed) return null; try { if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { const url = new URL(trimmed); const pathname = url.pathname; const parts = pathname.split('/').filter(Boolean); if (parts.length > 0) { return parts[parts.length - 1]; } } } catch (e) {} const parts = trimmed.split('/').filter(Boolean); if (parts.length > 0) { return parts[parts.length - 1]; } return trimmed; } // Hàm gộp tập phim để nhẩm tính số tập mới function checkEpisodesDiff(existingEpisodes, crawledEpisodes) { let addedCount = 0; const existingList = existingEpisodes || []; for (const crawledServer of crawledEpisodes) { let existingServer = existingList.find(s => s.server_name === crawledServer.server_name); if (!existingServer) { addedCount += crawledServer.server_data?.length || 0; continue; } const existingNames = new Set((existingServer.server_data || []).map(e => e.name)); for (const crawledEp of crawledServer.server_data || []) { if (!existingNames.has(crawledEp.name)) { addedCount++; } } } return addedCount; } // Dọn dẹp máy chủ 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; } 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 { type, urls, pageStart, pageEnd, countrySlug, genreSlug } = await request.json(); const localMovies = await getLocalMovies(); const slugsToCrawl = []; if (type === 'url') { const urlList = Array.isArray(urls) ? urls : [urls]; for (const rawUrl of urlList) { const slug = extractSlug(rawUrl); if (slug) { try { const res = await fetchWithTimeout(`https://phimapi.com/phim/${slug}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (data.status && data.movie) { const crawledMovie = data.movie; const crawledEpisodes = cleanAndFilterServers(data.episodes || []); let crawledEpCount = 0; if (crawledEpisodes.length > 0) { crawledEpCount = Math.max(...crawledEpisodes.map(s => s.server_data?.length || 0)); } const existing = localMovies[slug]; let expectedNew = crawledEpCount; let exists = false; let skip = false; let skipReason = ''; if (existing) { exists = true; const addedCount = checkEpisodesDiff(existing.episodes, crawledEpisodes); expectedNew = addedCount; if (addedCount === 0) { skip = true; skipReason = 'Đã đủ tập'; } } slugsToCrawl.push({ raw: crawledMovie.name, slug, totalEpisodes: crawledEpCount, expectedNew, exists, skip, skipReason }); } } catch (e) { slugsToCrawl.push({ raw: slug, slug, totalEpisodes: 0, expectedNew: 0, exists: false, skip: false, skipReason: `Lỗi quét: ${e.message}` }); } } } } else if (type === 'country') { const start = parseInt(pageStart || '1'); const end = parseInt(pageEnd || '1'); for (let p = start; p <= end; p++) { try { const res = await fetchWithTimeout(`https://phimapi.com/v1/api/quoc-gia/${countrySlug}?page=${p}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const resData = await res.json(); const items = resData.data?.items || []; for (const item of items) { if (item.slug && !slugsToCrawl.some(s => s.slug === item.slug)) { const existing = localMovies[item.slug]; let skip = false; let skipReason = ''; let expectedNew = 1; let exists = false; if (existing) { exists = true; const localStatus = (existing.movie?.episode_current || '').toLowerCase(); const remoteStatus = (item.episode_current || '').toLowerCase(); const isStatusEqual = localStatus === remoteStatus && remoteStatus !== ''; const isBothCompleted = (localStatus.includes('hoàn tất') || localStatus.includes('full') || localStatus.includes('complete') || localStatus.includes('12/12')) && (remoteStatus.includes('hoàn tất') || remoteStatus.includes('full') || remoteStatus.includes('complete') || remoteStatus.includes('12/12')); if (isStatusEqual || isBothCompleted) { skip = true; skipReason = remoteStatus.includes('hoàn tất') ? 'Phim đã hoàn tất' : 'Số tập trùng khớp'; expectedNew = 0; } } slugsToCrawl.push({ raw: item.name, slug: item.slug, totalEpisodes: item.episode_total || (item.episode_current ? item.episode_current : '?'), expectedNew, exists, skip, skipReason }); } } } catch (e) { console.error(`Error scanning page ${p}:`, e); } } } else if (type === 'genre') { const start = parseInt(pageStart || '1'); const end = parseInt(pageEnd || '1'); for (let p = start; p <= end; p++) { try { let apiUrl = `https://phimapi.com/v1/api/the-loai/${genreSlug}?page=${p}`; const listSlugs = ['hoat-hinh', 'phim-chieu-rap', 'phim-le', 'phim-bo', 'tv-shows']; if (listSlugs.includes(genreSlug)) { apiUrl = `https://phimapi.com/v1/api/danh-sach/${genreSlug}?page=${p}`; } const res = await fetchWithTimeout(apiUrl); if (!res.ok) throw new Error(`HTTP ${res.status}`); const resData = await res.json(); const items = resData.data?.items || []; for (const item of items) { if (item.slug && !slugsToCrawl.some(s => s.slug === item.slug)) { const existing = localMovies[item.slug]; let skip = false; let skipReason = ''; let expectedNew = 1; let exists = false; if (existing) { exists = true; const localStatus = (existing.movie?.episode_current || '').toLowerCase(); const remoteStatus = (item.episode_current || '').toLowerCase(); const isStatusEqual = localStatus === remoteStatus && remoteStatus !== ''; const isBothCompleted = (localStatus.includes('hoàn tất') || localStatus.includes('full') || localStatus.includes('complete') || localStatus.includes('12/12')) && (remoteStatus.includes('hoàn tất') || remoteStatus.includes('full') || remoteStatus.includes('complete') || remoteStatus.includes('12/12')); if (isStatusEqual || isBothCompleted) { skip = true; skipReason = remoteStatus.includes('hoàn tất') ? 'Phim đã hoàn tất' : 'Số tập trùng khớp'; expectedNew = 0; } } slugsToCrawl.push({ raw: item.name, slug: item.slug, totalEpisodes: item.episode_total || (item.episode_current ? item.episode_current : '?'), expectedNew, exists, skip, skipReason }); } } } catch (e) { console.error(`Error scanning genre page ${p}:`, e); } } } else if (type === 'batch') { const start = parseInt(pageStart || '1'); const end = parseInt(pageEnd || '1'); for (let p = start; p <= end; p++) { try { const res = await fetchWithTimeout(`https://phimapi.com/danh-sach/phim-moi-cap-nhat?page=${p}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const items = data.items || []; for (const item of items) { if (item.slug && !slugsToCrawl.some(s => s.slug === item.slug)) { const existing = localMovies[item.slug]; let skip = false; let skipReason = ''; let expectedNew = 1; let exists = false; if (existing) { exists = true; // Nhẩm tính thử } slugsToCrawl.push({ raw: item.name, slug: item.slug, totalEpisodes: item.episode_total || '?', expectedNew, exists, skip, skipReason }); } } } catch (e) { console.error(`Error scanning batch page ${p}:`, e); } } } else if (type === 'all') { // Vì scan all có thể mất thời gian nếu lấy hết nghìn slugs, ta chỉ quét trang 1-2 để đảm bảo không timeout // Hoặc trả về nhanh 1 danh sách const res = await fetchWithTimeout(`https://phimapi.com/danh-sach/phim-moi-cap-nhat?page=1`); if (res.ok) { const data = await res.json(); const items = data.items || []; for (const item of items) { slugsToCrawl.push({ raw: item.name, slug: item.slug, totalEpisodes: '?', expectedNew: 1, exists: !!localMovies[item.slug], skip: false, skipReason: '' }); } } } return new Response(JSON.stringify({ success: true, count: slugsToCrawl.length, slugs: slugsToCrawl }), { headers: { 'Content-Type': 'application/json' } }); } catch (err) { return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }