/** * mock TMDB 固定数据。电影/剧集 ID 取自公开 TMDB 条目以便对照,但标题/集数/日期只保证"合理", * 不作为真实数据的权威来源;国产综艺条目 ID(900001)为虚构。 */ export interface TmdbFixtureEpisode { episode_number: number; air_date: string | null; /** 按语言的集名;请求语言没有时 mock 返回 TMDB 式占位名("第 N 集" / "Episode N") */ names: Record; /** MD-1:按语言的简介;没有时返回空串 */ overviews?: Record; runtime?: number | null; still_path?: string | null; vote_average?: number; vote_count?: number; } export interface TmdbFixtureSeason { season_number: number; air_date: string | null; names: Record; overviews?: Record; poster_path?: string | null; episodes: TmdbFixtureEpisode[]; } /** MD-1 富详情字段(credits / 类型 / 评分 / 状态…);缺省不返回 */ export interface TmdbFixtureRich { genres?: string[]; vote_average?: number; vote_count?: number; status?: string; runtime?: number; episode_run_time?: number[]; last_air_date?: string; backdrop_path?: string; networks?: string[]; tagline?: Record; cast?: { id: number; name: string; character: string; profile_path: string | null }[]; crew?: { id: number; name: string; job: string; profile_path: string | null }[]; created_by?: { id: number; name: string; profile_path: string | null }[]; } export interface TmdbFixture { type: "movie" | "tv"; id: number; original_title: string; original_language: string; origin_country: string[]; date: string; titles: Record; overviews?: Record; popularity: number; alternative_titles: { iso_3166_1: string; title: string; type: string }[]; external_ids: { imdb_id?: string | null; tvdb_id?: number | null }; /** 额外译名(titles 中的语言会自动生成 translations);title 为空串表示该语言只有简介没有译名(TMDB 常见) */ extra_translations?: { iso_639_1: string; iso_3166_1: string; title: string; overview?: string }[]; rich?: TmdbFixtureRich; seasons?: TmdbFixtureSeason[]; /** 海报路径(拼 image.tmdb.org);缺省 null */ poster_path?: string | null; /** 列表接口(trending / discover / 榜单)返回的 genre_ids;缺省空 */ genre_ids?: number[]; /** 剧集组(`/tv/{id}/episode_groups`、`/tv/episode_group/{id}`);组内集按 TMDB 默认季集号引用 seasons 里的集 */ episode_groups?: TmdbFixtureEpisodeGroup[]; /** 标志(append_to_response=images 与 `/{type}/{id}/images` 按 include_image_language 过滤);缺省没有 */ logos?: { file_path: string; iso_639_1: string | null; vote_average?: number; vote_count?: number }[]; } export interface TmdbFixtureEpisodeGroup { id: string; name: string; type: number; description?: string; network?: string | null; groups: { id?: string; name: string; order: number; episodes: { season_number: number; episode_number: number }[] }[]; } function eps(n: number, start: string, zh: (i: number) => string, en: (i: number) => string): TmdbFixtureEpisode[] { const base = Date.parse(start); return Array.from({ length: n }, (_, k) => ({ episode_number: k + 1, air_date: new Date(base + k * 7 * 86400_000).toISOString().slice(0, 10), names: { "zh-CN": zh(k + 1), "en-US": en(k + 1) }, })); } export const TMDB_FIXTURES: TmdbFixture[] = [ { type: "movie", id: 872585, genre_ids: [18, 36], original_title: "Oppenheimer", original_language: "en", origin_country: ["US"], date: "2023-07-19", titles: { "zh-CN": "奥本海默", "en-US": "Oppenheimer" }, overviews: { "zh-CN": "原子弹之父的故事。", "en-US": "The story of J. Robert Oppenheimer." }, popularity: 120.5, alternative_titles: [ { iso_3166_1: "TW", title: "奧本海默", type: "" }, { iso_3166_1: "CN", title: "奥本海默传", type: "" }, ], external_ids: { imdb_id: "tt15398776" }, poster_path: "/oppenheimer.jpg", extra_translations: [{ iso_639_1: "ja", iso_3166_1: "JP", title: "オッペンハイマー" }], }, { type: "movie", id: 693134, genre_ids: [878, 12], original_title: "Dune: Part Two", original_language: "en", origin_country: ["US"], date: "2024-02-27", titles: { "zh-CN": "沙丘2", "en-US": "Dune: Part Two" }, popularity: 210.1, alternative_titles: [ { iso_3166_1: "US", title: "Dune 2", type: "" }, { iso_3166_1: "CN", title: "沙丘:第二部", type: "" }, { iso_3166_1: "TW", title: "沙丘:第二部", type: "" }, ], external_ids: { imdb_id: "tt15239678" }, }, { type: "movie", id: 842675, genre_ids: [878, 28], original_title: "流浪地球2", original_language: "zh", origin_country: ["CN"], date: "2023-01-22", titles: { "zh-CN": "流浪地球2", "en-US": "The Wandering Earth II" }, popularity: 45.3, alternative_titles: [ { iso_3166_1: "US", title: "The Wandering Earth 2", type: "" }, { iso_3166_1: "CN", title: "流浪地球 2", type: "" }, ], external_ids: { imdb_id: "tt13539646" }, }, { type: "tv", id: 136315, genre_ids: [35, 18], original_title: "The Bear", original_language: "en", origin_country: ["US"], date: "2022-06-23", titles: { "zh-CN": "熊家餐馆", "en-US": "The Bear" }, popularity: 88.2, alternative_titles: [ { iso_3166_1: "TW", title: "大熊餐廳", type: "" }, { iso_3166_1: "CN", title: "熊家小馆", type: "" }, ], external_ids: { imdb_id: "tt14452776", tvdb_id: 401654 }, seasons: [ { season_number: 1, air_date: "2022-06-23", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, episodes: eps( 8, "2022-06-23", (i) => `第 ${i} 集`, (i) => `Episode ${i}`, ), }, { season_number: 2, air_date: "2023-06-22", names: { "zh-CN": "第 2 季", "en-US": "Season 2" }, episodes: eps( 10, "2023-06-22", (i) => `第 ${i} 集`, (i) => `Episode ${i}`, ), }, { season_number: 3, air_date: "2024-06-26", names: { "zh-CN": "第 3 季", "en-US": "Season 3" }, episodes: eps( 10, "2024-06-26", (i) => `第 ${i} 集`, (i) => `Episode ${i}`, ), }, ], }, { type: "tv", id: 100088, genre_ids: [18, 10765], original_title: "The Last of Us", original_language: "en", origin_country: ["US"], date: "2023-01-15", titles: { "zh-CN": "最后生还者", "en-US": "The Last of Us" }, popularity: 150.7, alternative_titles: [ { iso_3166_1: "CN", title: "最后的生还者", type: "" }, { iso_3166_1: "TW", title: "最後生還者", type: "" }, ], external_ids: { imdb_id: "tt3581920", tvdb_id: 392256 }, seasons: [ { season_number: 1, air_date: "2023-01-15", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, episodes: eps( 9, "2023-01-15", (i) => `第 ${i} 集`, (i) => `Episode ${i}`, ), }, ], }, { type: "tv", id: 900001, genre_ids: [10764], original_title: "向往的生活", original_language: "zh", origin_country: ["CN"], date: "2017-01-15", titles: { "zh-CN": "向往的生活", "en-US": "Back to Field" }, popularity: 12.4, alternative_titles: [ { iso_3166_1: "CN", title: "向往的生活 第一季", type: "" }, { iso_3166_1: "CN", title: "向往", type: "简称" }, ], external_ids: { imdb_id: null, tvdb_id: null }, seasons: [ { season_number: 0, air_date: "2017-01-10", names: { "zh-CN": "特别篇", "en-US": "Specials" }, episodes: eps( 2, "2017-01-10", (i) => `先导片 ${i}`, (i) => `Special ${i}`, ), }, { season_number: 1, air_date: "2017-01-15", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, episodes: eps( 12, "2017-01-15", (i) => `第 ${i} 期`, (i) => `Episode ${i}`, ), }, { season_number: 2, air_date: "2018-04-20", names: { "zh-CN": "第 2 季", "en-US": "Season 2" }, episodes: eps( 3, "2018-04-20", (i) => `第 ${i} 期`, (i) => `Episode ${i}`, ), }, ], }, // MD-1 语言回退链:zh-CN 译名为空(只有简介)、CN 地区别名是简中名、zh-TW / zh-HK 有译名(对照 TMDB tv/95396 真实形态) { type: "tv", id: 95396, genre_ids: [18, 9648], original_title: "Severance", original_language: "en", origin_country: ["US"], date: "2022-02-17", titles: { "en-US": "Severance", "zh-TW": "人生切割術", "zh-HK": "生活割離術" }, overviews: { "en-US": "Mark leads a team of office workers.", "zh-TW": "馬克帶領的辦公團隊成員全都接受手術。" }, popularity: 95.1, alternative_titles: [ { iso_3166_1: "CN", title: "人生切割术", type: "" }, { iso_3166_1: "HK", title: "生活割離術", type: "" }, ], external_ids: { imdb_id: "tt11280740", tvdb_id: 371980 }, extra_translations: [ { iso_639_1: "zh", iso_3166_1: "CN", title: "", overview: "故事发生在一家名为卢蒙的超级大公司内。" }, { iso_639_1: "zh", iso_3166_1: "SG", title: "", overview: "马克领导着一个办公室里的团队。" }, ], poster_path: "/severance.jpg", rich: { genres: ["剧情", "悬疑", "Sci-Fi & Fantasy"], vote_average: 8.395, vote_count: 2400, status: "Returning Series", episode_run_time: [], last_air_date: "2025-03-21", backdrop_path: "/severance-bg.jpg", networks: ["Apple TV"], cast: [ { id: 4495, name: "亚当·斯科特", character: "Mark Scout", profile_path: "/adam.jpg" }, { id: 1319469, name: "布丽特·洛薇尔", character: "Helly Riggs", profile_path: null }, ], created_by: [{ id: 3442409, name: "丹·埃里克森", profile_path: "/dan.jpg" }], }, seasons: [ { season_number: 1, air_date: "2022-02-17", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, overviews: { "zh-CN": "在卢蒙工业公司,员工们接受了一种名为“分离”的手术。" }, poster_path: "/sev-s1.jpg", episodes: [ { episode_number: 1, air_date: "2022-02-17", names: { "zh-CN": "有关地狱的好消息", "en-US": "Good News About Hell" }, overviews: { "zh-CN": "马克升职成为部门主管。", "en-US": "Mark is promoted." }, runtime: 59, still_path: "/sev-101.jpg", vote_average: 8.2, vote_count: 171, }, { episode_number: 2, air_date: "2022-02-17", names: { "zh-TW": "半循環", "en-US": "Half Loop" }, overviews: { "zh-TW": "馬克帶領新員工參觀。", "en-US": "Mark gives Helly a tour." }, runtime: 53, still_path: "/sev-102.jpg", vote_average: 8.0, vote_count: 150, }, { episode_number: 3, air_date: "2022-02-25", names: { "en-US": "In Perpetuity" }, overviews: {}, runtime: 52, still_path: null, }, ], }, { season_number: 2, air_date: "2099-01-17", names: { "zh-CN": "第 2 季", "en-US": "Season 2" }, episodes: [{ episode_number: 1, air_date: "2099-01-17", names: {} }], }, ], }, ]; /** * MD-2:《心动的信号》(TMDB tv/103147 的真实形态):作品与每期标题有 zh-CN,简介只有 en-US(实为标题英译)。 * 不在 TMDB_FIXTURES 里,用到的测试自行拼进 fixtures。 */ export const HEART_SIGNAL_FIXTURE: TmdbFixture = { type: "tv", id: 103147, original_title: "心动的信号", original_language: "zh", origin_country: ["CN"], date: "2018-08-01", titles: { "zh-CN": "心动的信号", "en-US": "Heart Signal" }, overviews: { "en-US": "A Chinese dating reality show." }, popularity: 6.2, alternative_titles: [], external_ids: { imdb_id: null, tvdb_id: null }, rich: { genres: ["真人秀"], status: "Returning Series" }, seasons: [ { season_number: 1, air_date: "2018-08-01", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, episodes: [ { episode_number: 1, air_date: "2018-08-01", names: { "zh-CN": "心动不如行动,大胆亮出底牌!", "en-US": "Episode 1" }, overviews: { "en-US": "If Your Heart Skips a Beat, Play Your Cards Openly!" }, }, { episode_number: 2, air_date: "2018-08-08", names: { "zh-CN": "第 2 集", "en-US": "Who Is the One?" }, overviews: { "en-US": "Who Is the One?" }, }, { episode_number: 3, air_date: "2018-08-15", names: { "zh-CN": "约会之夜", "en-US": "Date Night" }, overviews: { "zh-TW": "約會之夜。", "en-US": "Date Night." }, }, ], }, ], }; /** * 两个 cour 被 TMDB 默认季合成一季的番剧(剧集组测试用):默认 S1 共 25 集(含季 0 特别篇 1 集); * 剧集组「制作季」把它拆成 S1(1–12)、S2(13–25),不含特别篇;剧集组「播出顺序」把全部 25 集放在一季。 * 不在 TMDB_FIXTURES 里,用到的测试自行拼进 fixtures。 */ export const SPLIT_COUR_FIXTURE: TmdbFixture = { type: "tv", id: 990001, original_title: "Split Cour", original_language: "ja", origin_country: ["JP"], date: "2024-01-07", titles: { "zh-CN": "分割番", "en-US": "Split Cour" }, overviews: { "zh-CN": "两个 cour 的番剧。" }, popularity: 30, alternative_titles: [], external_ids: { imdb_id: null, tvdb_id: null }, rich: { genres: ["动画"], status: "Ended" }, poster_path: "/split.jpg", seasons: [ { season_number: 0, air_date: "2024-12-25", names: { "zh-CN": "特别篇", "en-US": "Specials" }, episodes: [{ episode_number: 1, air_date: "2024-12-25", names: { "zh-CN": "圣诞特别篇", "en-US": "Xmas" } }], }, { season_number: 1, air_date: "2024-01-07", names: { "zh-CN": "第 1 季", "en-US": "Season 1" }, poster_path: "/split-s1.jpg", episodes: [ ...eps( 12, "2024-01-07", (i) => `上篇第${i}话`, (i) => `Part1 Ep${i}`, ), ...eps( 13, "2024-07-07", (i) => `下篇第${i}话`, (i) => `Part2 Ep${i}`, ).map((e, k) => ({ ...e, episode_number: 13 + k, })), ], }, ], episode_groups: [ { id: "grp-production", name: "制作季", type: 6, network: "TOKYO MX", groups: [ { id: "g1", name: "第一部", order: 1, episodes: Array.from({ length: 12 }, (_, k) => ({ season_number: 1, episode_number: k + 1 })), }, { id: "g2", name: "第二部", order: 2, episodes: Array.from({ length: 13 }, (_, k) => ({ season_number: 1, episode_number: 13 + k })), }, ], }, { id: "grp-aired", name: "播出顺序", type: 1, groups: [ { id: "a1", name: "全集", order: 1, episodes: Array.from({ length: 25 }, (_, k) => ({ season_number: 1, episode_number: k + 1 })), }, ], }, ], };