feat(tmdb): language fallback chain, rich details and per-episode season fetch (MD-1)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:28:16 +08:00
1 parent c3aaf901ad
commit fc1856c2e3
8 files changed
+834 -5

No files matched your search

+40 -3
View File
@@ -107,17 +107,27 @@ export async function createMockTmdb(opts: MockTmdbOptions = {}): Promise<MockTm
const f = find("tv", m[1]!);
const s = f?.seasons?.find((x) => String(x.season_number) === m[2]);
if (!f || !s) return notFound;
const lang = q.language ?? "en-US";
const zh = lang.startsWith("zh");
return {
json: {
id: f.id * 100 + s.season_number,
season_number: s.season_number,
name: pick(s.names, q.language, `Season ${s.season_number}`),
overview: s.overviews?.[lang] ?? "",
poster_path: s.poster_path ?? null,
air_date: s.air_date,
episodes: s.episodes.map((e) => ({
episode_number: e.episode_number,
season_number: s.season_number,
name: pick(e.names, q.language, ""),
// TMDB 缺该语言译名时给占位名
name: e.names[lang] ?? (zh ? `第 ${e.episode_number} 集` : `Episode ${e.episode_number}`),
overview: e.overviews?.[lang] ?? "",
air_date: e.air_date,
runtime: e.runtime ?? null,
still_path: e.still_path ?? null,
vote_average: e.vote_average ?? 0,
vote_count: e.vote_count ?? 0,
})),
},
};
@@ -130,6 +140,27 @@ export async function createMockTmdb(opts: MockTmdbOptions = {}): Promise<MockTm
if (!f) return notFound;
const append = new Set((q.append_to_response ?? "").split(",").filter(Boolean));
const out: Record<string, unknown> = { ...brief(f, q.language), origin_country: f.origin_country };
const r = f.rich;
if (r) {
out.genres = (r.genres ?? []).map((name, i) => ({ id: i + 1, name }));
out.vote_average = r.vote_average ?? 0;
out.vote_count = r.vote_count ?? 0;
out.status = r.status ?? null;
out.backdrop_path = r.backdrop_path ?? null;
out.tagline = r.tagline?.[q.language ?? "en-US"] ?? "";
if (f.type === "movie") out.runtime = r.runtime ?? null;
else {
out.episode_run_time = r.episode_run_time ?? [];
out.last_air_date = r.last_air_date ?? null;
out.networks = (r.networks ?? []).map((name, i) => ({ id: i + 1, name }));
out.created_by = r.created_by ?? [];
out.number_of_episodes = (f.seasons ?? [])
.filter((x) => x.season_number > 0)
.reduce((a, x) => a + x.episodes.length, 0);
}
if (append.has("credits"))
out.credits = { cast: (r.cast ?? []).map((c, order) => ({ ...c, order })), crew: r.crew ?? [] };
}
if (f.type === "tv") {
out.number_of_seasons = f.seasons?.filter((s) => s.season_number > 0).length ?? 0;
out.seasons = (f.seasons ?? []).map((s) => ({
@@ -137,6 +168,8 @@ export async function createMockTmdb(opts: MockTmdbOptions = {}): Promise<MockTm
episode_count: s.episodes.length,
name: pick(s.names, q.language, ""),
air_date: s.air_date,
overview: s.overviews?.[q.language ?? "en-US"] ?? "",
poster_path: s.poster_path ?? null,
}));
}
if (append.has("alternative_titles")) {
@@ -150,12 +183,16 @@ export async function createMockTmdb(opts: MockTmdbOptions = {}): Promise<MockTm
translations: [
...Object.entries(f.titles).map(([lang, t]) => {
const [l, r] = lang.split("-");
return { iso_639_1: l, iso_3166_1: r ?? "", data: { [key]: t } };
return {
iso_639_1: l,
iso_3166_1: r ?? "",
data: { [key]: t, overview: f.overviews?.[lang] ?? "" },
};
}),
...(f.extra_translations ?? []).map((x) => ({
iso_639_1: x.iso_639_1,
iso_3166_1: x.iso_3166_1,
data: { [key]: x.title },
data: { [key]: x.title, overview: x.overview ?? "" },
})),
],
};