dptxa-proxy / src /pages /api /admin /crawl-single-episode.js
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
7.75 kB
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' } });
}
}