Files
2026-06-18 17:13:24 +02:00

141 lines
3.8 KiB
TypeScript

const TMDB_BASE = "https://api.themoviedb.org/3";
const LANGUAGE = "en-US";
export interface TmdbSearchResult {
id: number;
title: string;
release_date: string;
poster_path: string | null;
overview: string;
}
export interface TmdbMovieDetails {
id: number;
title: string;
release_date: string;
runtime: number | null;
overview: string;
poster_path: string | null;
backdrop_path: string | null;
genres: Array<{ id: number; name: string }>;
budget: number;
revenue: number;
production_countries: Array<{ iso_3166_1: string; name: string }>;
production_companies: Array<{ name: string }>;
spoken_languages: Array<{ english_name: string }>;
release_dates?: {
results: Array<{
iso_3166_1: string;
release_dates: Array<{
certification: string;
type: number;
note: string;
}>;
}>;
};
keywords?: {
keywords: Array<{ id: number; name: string }>;
};
}
function getApiKey(): string {
const key = process.env.TMDB_API_KEY;
if (!key) {
throw new Error("TMDB_API_KEY is not set");
}
return key;
}
async function tmdbFetch<T>(path: string, params: Record<string, string> = {}) {
const url = new URL(`${TMDB_BASE}${path}`);
url.searchParams.set("api_key", getApiKey());
url.searchParams.set("language", LANGUAGE);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
const response = await fetch(url.toString(), {
next: { revalidate: 3600 },
});
if (!response.ok) {
throw new Error(`TMDB request failed: ${response.status}`);
}
return response.json() as Promise<T>;
}
export async function searchMovies(query: string): Promise<TmdbSearchResult[]> {
if (!query.trim()) {
return [];
}
const data = await tmdbFetch<{ results: TmdbSearchResult[] }>("/search/movie", {
query: query.trim(),
include_adult: "false",
});
return data.results.slice(0, 8);
}
export async function getMovieDetails(movieId: number): Promise<TmdbMovieDetails> {
return tmdbFetch<TmdbMovieDetails>(`/movie/${movieId}`, {
append_to_response: "release_dates,keywords",
});
}
export function getPosterUrl(path: string | null, size: "w185" | "w500" | "original" = "w500") {
if (!path) {
return null;
}
return `https://image.tmdb.org/t/p/${size}${path}`;
}
export function getUsCertification(movie: TmdbMovieDetails): string | null {
const us = movie.release_dates?.results.find((r) => r.iso_3166_1 === "US");
if (!us) {
return null;
}
const theatrical = us.release_dates.find((r) => r.type === 3 && r.certification);
if (theatrical?.certification) {
return theatrical.certification;
}
const any = us.release_dates.find((r) => r.certification);
return any?.certification ?? null;
}
export function formatMovieContext(movie: TmdbMovieDetails): string {
const genres = movie.genres.map((g) => g.name).join(", ");
const keywords = movie.keywords?.keywords.map((k) => k.name).join(", ") ?? "";
const certification = getUsCertification(movie);
const releaseDates = movie.release_dates?.results
.map((country) => {
const certs = country.release_dates
.filter((r) => r.certification)
.map((r) => r.certification);
const unique = [...new Set(certs)];
if (unique.length === 0) {
return null;
}
return `${country.iso_3166_1}: ${unique.join(", ")}`;
})
.filter(Boolean)
.join("\n");
return [
`Title: ${movie.title}`,
`Year: ${movie.release_date?.slice(0, 4) ?? "Unknown"}`,
`Runtime: ${movie.runtime ?? "Unknown"} minutes`,
`Genres: ${genres}`,
`Overview: ${movie.overview}`,
certification ? `US Certification: ${certification}` : null,
keywords ? `Keywords: ${keywords}` : null,
releaseDates ? `International certifications:\n${releaseDates}` : null,
]
.filter(Boolean)
.join("\n");
}