/** * Fanart.tv client. Requires a personal API key (free, no email required * but you do need to register at https://fanart.tv/get-an-api-key/ and * obtain a "personal" key). The key is stored in user preferences and * supplied to every request. * * Most-useful endpoints for our chrome: * - /v3/movies/{tmdb_or_imdb_id} movie artwork (logos, clearart, banners) * - /v3/tv/{tvdb_id} TV artwork (logos, clearart, banners) * * Each artwork item carries `lang` so we can pick English versions first * when present, falling back to whatever the highest-voted entry is. */ const BASE = 'https://webservice.fanart.tv/v3' export interface FanartImage { id: string url: string lang?: string likes?: string /* Some artwork types include season number (TV) */ season?: string } export interface FanartMovieResponse { name?: string tmdb_id?: string imdb_id?: string hdmovielogo?: FanartImage[] hdmovieclearart?: FanartImage[] movielogo?: FanartImage[] movieart?: FanartImage[] movieposter?: FanartImage[] moviebackground?: FanartImage[] moviedisc?: FanartImage[] moviebanner?: FanartImage[] moviethumb?: FanartImage[] } export interface FanartTvResponse { name?: string thetvdb_id?: string hdtvlogo?: FanartImage[] clearlogo?: FanartImage[] hdclearart?: FanartImage[] clearart?: FanartImage[] showbackground?: FanartImage[] tvthumb?: FanartImage[] tvbanner?: FanartImage[] characterart?: FanartImage[] seasonposter?: FanartImage[] seasonbanner?: FanartImage[] seasonthumb?: FanartImage[] tvposter?: FanartImage[] } async function fetchJson(url: string, apiKey: string): Promise { if (!apiKey) return null try { const res = await fetch(url, { headers: { 'api-key': apiKey } }) if (!res.ok) return null return (await res.json()) as T } catch { return null } } export function fanartMovie(idTmdbOrImdb: string, apiKey: string): Promise { if (!idTmdbOrImdb) return Promise.resolve(null) return fetchJson( `${BASE}/movies/${encodeURIComponent(idTmdbOrImdb)}`, apiKey, ) } export function fanartTv(tvdbId: string, apiKey: string): Promise { if (!tvdbId) return Promise.resolve(null) return fetchJson(`${BASE}/tv/${encodeURIComponent(tvdbId)}`, apiKey) } /** * Pick the best image from a list: prefer English entries, then * highest `likes` count, falling back to first in list. */ export function pickBestFanartImage(images?: FanartImage[]): FanartImage | null { if (!images || images.length === 0) return null const eng = images.filter(i => (i.lang || '').toLowerCase() === 'en') const pool = eng.length > 0 ? eng : images return [...pool].sort((a, b) => Number(b.likes ?? 0) - Number(a.likes ?? 0))[0] || null }