dptxa-proxy / src /lib /api.js
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
6.55 kB
import { txasupabase } from './txasupabase.js';
const getLocalMovies = () => txasupabase.getLocalMovies();
const getDeletedMovies = () => txasupabase.getDeletedMovies();
export { formatImageUrl } from './image.js';
// Hàm định dạng phim cục bộ thành định dạng danh sách rút gọn
function mapLocalToListItem(localMovie) {
return {
_id: localMovie.movie.id || localMovie.movie.slug,
name: localMovie.movie.name,
slug: localMovie.movie.slug,
origin_name: localMovie.movie.origin_name,
thumb_url: localMovie.movie.thumb_url,
poster_url: localMovie.movie.poster_url,
year: localMovie.movie.year,
quality: localMovie.movie.quality || 'FHD',
lang: localMovie.movie.lang || 'Vietsub',
episode_current: localMovie.movie.episode_current || 'Tập 1',
type: localMovie.movie.type || 'single',
isLocal: true
};
}
// Trích tên gốc phim (bỏ Phần/Part/Season/SS/EP suffix)
function getBaseMovieName(name) {
if (!name) return '';
let clean = name;
// 1. Xóa các cụm chứa ngoặc đơn hoặc ngoặc vuông chứa Phần/Part/Season (ví dụ: "(Phần 2)", "[Season 3]")
clean = clean.replace(/\s*[([\])]*\s*(Phần|Part|Season|SS|EP|Ep|Tập)\s*(\d+|[IVXLCDM]+)\s*[([\])]*/gi, '');
// 2. Xóa các cụm Phần/Part/Season đứng độc lập ở cuối
clean = clean.replace(/\s*[-–—|:]?\s*(Phần|Part|Season|SS|EP|Ep|Tập)\s*(\d+|[IVXLCDM]+)\b/gi, '');
// 3. Xóa các số hoặc ký tự số La Mã cô độc ở cuối
clean = clean.replace(/\s*[-–—|:]?\s*(\d+|[IVXLCDM]+)$/gi, '');
// 4. Dọn dẹp sạch các dấu ngoặc đơn/ngoặc vuông rỗng còn sót lại ở cuối chuỗi
clean = clean.replace(/\s*[([\])]+\s*$/gi, '');
return clean.trim().toLowerCase();
}
// Nhóm phim theo tên gốc, giữ lại phần mới nhất cho listing
function groupMoviesBySeason(movies) {
const groups = new Map();
for (const m of movies) {
const base = getBaseMovieName(m.movie.name);
if (!base || base.length <= 2) {
// Tên quá ngắn, không nhóm
groups.set(m.movie.slug, m);
continue;
}
const existing = groups.get(base);
if (!existing) {
groups.set(base, m);
} else {
// Giữ phần cập nhật mới nhất
const existingTime = new Date(existing.movie.modified?.time || 0).getTime();
const currentTime = new Date(m.movie.modified?.time || 0).getTime();
if (currentTime > existingTime) {
groups.set(base, m);
}
}
}
return Array.from(groups.values());
}
// Hàm lọc và phân trang phim cục bộ
// grouped = true: gộp phim cùng tên khác phần (mặc định cho trang công khai)
// grouped = false: không gộp (dùng cho trang admin)
async function queryLocalMovies(filterFn, page = 1, limit = 12, { grouped = true } = {}) {
const localMovies = await getLocalMovies();
const deletedSlugs = await getDeletedMovies();
let list = Object.values(localMovies)
.filter(m => !deletedSlugs.includes(m.movie.slug));
if (filterFn) {
list = list.filter(filterFn);
}
// Sắp xếp theo ngày cập nhật mới nhất (modified time)
list.sort((a, b) => {
const timeA = new Date(a.movie.modified?.time || 0).getTime();
const timeB = new Date(b.movie.modified?.time || 0).getTime();
return timeB - timeA;
});
// Nhóm phim theo season/part — chỉ hiển thị phần mới nhất trong listing
if (grouped) {
list = groupMoviesBySeason(list);
}
const totalItems = list.length;
const totalPages = Math.ceil(totalItems / limit) || 1;
const paginated = list.slice((page - 1) * limit, page * limit)
.map(mapLocalToListItem);
return {
items: paginated,
pagination: {
totalItems,
totalItemsPerPage: limit,
currentPage: page,
totalPages
}
};
}
/**
* Lấy danh sách phim mới cập nhật
* @param {number} page
* @param {{ grouped?: boolean }} options - set grouped: false for admin pages
*/
export async function getNewestMovies(page = 1, options = {}) {
return await queryLocalMovies(null, page, 12, options);
}
/**
* Lấy chi tiết phim theo slug
*/
export async function getMovieDetail(slug) {
const deletedSlugs = await getDeletedMovies();
if (deletedSlugs.includes(slug)) {
return null;
}
const localMovies = await getLocalMovies();
if (localMovies[slug]) {
return {
status: true,
movie: localMovies[slug].movie,
episodes: localMovies[slug].episodes || []
};
}
return null;
}
export async function getSeriesMovies(page = 1) {
return await queryLocalMovies(m => m.movie.type === 'series', page, 12);
}
export async function getSingleMovies(page = 1) {
return await queryLocalMovies(m => m.movie.type === 'single', page, 12);
}
export async function getCartoonMovies(page = 1) {
return await queryLocalMovies(m => m.movie.type === 'hoat-hinh', page, 12);
}
export async function getTvShows(page = 1) {
return await queryLocalMovies(m => m.movie.type === 'tvshows' || m.movie.type === 'tv-shows', page, 12);
}
export async function getMoviesByCategory(category, page = 1) {
const defaultTypes = {
'phim-bo': 'series',
'phim-le': 'single',
'hoat-hinh': 'hoat-hinh',
'tv-shows': 'tv-shows'
};
if (defaultTypes[category]) {
return await queryLocalMovies(m => m.movie.type === defaultTypes[category], page, 12);
}
return await queryLocalMovies(m =>
m.movie.category?.some(c => c.slug === category), page, 12
);
}
export async function getMoviesByCountry(country, page = 1) {
return await queryLocalMovies(m =>
m.movie.country?.some(c => c.slug === country), page, 12
);
}
export async function searchMovies(keyword, page = 1, options = {}) {
if (!keyword) {
return { items: [], pagination: { totalItems: 0, totalItemsPerPage: 12, currentPage: page, totalPages: 1 } };
}
const k = keyword.toLowerCase();
return await queryLocalMovies(m =>
m.movie.name?.toLowerCase().includes(k) ||
m.movie.origin_name?.toLowerCase().includes(k) ||
m.movie.slug?.includes(k), page, 12, options
);
}
export async function getMoviesByTopic(topicSlug, page = 1) {
const { TOPICS } = await import('./topics.js');
const topic = TOPICS.find(t => t.slug === topicSlug);
if (!topic) {
return { items: [], pagination: { totalItems: 0, totalItemsPerPage: 12, currentPage: page, totalPages: 1 } };
}
return await queryLocalMovies(topic.filter, page, 12);
}