Homelabtoolkit v2
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user