import { readFileSync, existsSync } from 'fs'; import { resolve } from 'path'; const CONFIG_PATH = resolve('config.json'); const TMDB_BASE = 'https://api.themoviedb.org/3'; function loadConfig() { if (!existsSync(CONFIG_PATH)) return {}; try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; } } export function loadTmdbConfig() { const config = loadConfig(); return { tmdbApiKey: String(config?.tmdbApiKey || '').trim() }; } export function hasTmdbConfig() { return !!loadTmdbConfig().tmdbApiKey; } async function fetchTmdb(pathname, params = {}) { const { tmdbApiKey } = loadTmdbConfig(); if (!tmdbApiKey) { throw new Error('TMDB API key not configured'); } const url = new URL(`${TMDB_BASE}${pathname.startsWith('/') ? pathname : `/${pathname}`}`); for (const [key, value] of Object.entries({ api_key: tmdbApiKey, language: 'en-US', include_adult: 'false', ...params })) { if (value === undefined || value === null || value === '') continue; url.searchParams.set(key, String(value)); } const response = await fetch(url, { headers: { Accept: 'application/json' } }); if (!response.ok) { const text = await response.text().catch(() => response.statusText); throw new Error(text || `${response.status} ${response.statusText}`); } return response.json(); } function normalizeTmdbSearchItem(item, mediaType) { return { tmdbId: Number(item?.id || 0) || null, name: item?.title || item?.name || '', year: Number(String(item?.release_date || item?.first_air_date || '').slice(0, 4)) || null, mediaType, overview: item?.overview || '', genreIds: Array.isArray(item?.genre_ids) ? item.genre_ids : [], originalLanguage: item?.original_language || '', popularity: Number(item?.popularity || 0), voteAverage: Number(item?.vote_average || 0), voteCount: Number(item?.vote_count || 0) }; } export async function searchTmdbByTitle({ mediaType, name, year }) { const searchType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/search/${searchType}`, { query: name, page: 1, ...(year ? (searchType === 'movie' ? { year } : { first_air_date_year: year }) : {}) }); return (payload?.results || []).map((item) => normalizeTmdbSearchItem(item, searchType)); } export async function fetchTmdbSimilar({ mediaType, tmdbId, page = 1 }) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/${pathType}/${encodeURIComponent(tmdbId)}/similar`, { page }); return (payload?.results || []).map((item) => normalizeTmdbSearchItem(item, pathType)); } export async function fetchTmdbRecommendations({ mediaType, tmdbId, page = 1 }) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/${pathType}/${encodeURIComponent(tmdbId)}/recommendations`, { page }); return (payload?.results || []).map((item) => normalizeTmdbSearchItem(item, pathType)); } export async function fetchTmdbDetails({ mediaType, tmdbId }) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; return fetchTmdb(`/${pathType}/${encodeURIComponent(tmdbId)}`); } export async function fetchTmdbKeywords({ mediaType, tmdbId }) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/${pathType}/${encodeURIComponent(tmdbId)}/keywords`); return payload?.keywords || payload?.results || []; } export async function fetchTmdbCredits({ mediaType, tmdbId }) { if (mediaType === 'tv') { return fetchTmdb(`/tv/${encodeURIComponent(tmdbId)}/aggregate_credits`); } return fetchTmdb(`/movie/${encodeURIComponent(tmdbId)}/credits`); } export async function fetchTmdbGenres(mediaType) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/genre/${pathType}/list`, { language: 'en' }); return payload?.genres || []; } export async function fetchTmdbDiscover({ mediaType, params = {} }) { const pathType = mediaType === 'tv' ? 'tv' : 'movie'; const payload = await fetchTmdb(`/discover/${pathType}`, params); return (payload?.results || []).map((item) => normalizeTmdbSearchItem(item, pathType)); }