api clients for jellyfin, tmdb, fanart, etc.

This commit is contained in:
2026-03-23 13:32:13 +02:00
parent d65da148d4
commit 292b3f42cf
14 changed files with 2903 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* 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<T>(url: string, apiKey: string): Promise<T | null> {
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<FanartMovieResponse | null> {
if (!idTmdbOrImdb) return Promise.resolve(null)
return fetchJson<FanartMovieResponse>(
`${BASE}/movies/${encodeURIComponent(idTmdbOrImdb)}`,
apiKey,
)
}
export function fanartTv(tvdbId: string, apiKey: string): Promise<FanartTvResponse | null> {
if (!tvdbId) return Promise.resolve(null)
return fetchJson<FanartTvResponse>(`${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
}