124 lines
4.0 KiB
JavaScript
124 lines
4.0 KiB
JavaScript
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));
|
|
}
|