feat(adapters): TMDB 列表与筛选、豆瓣榜单 / 标签 / 筛选 / 搜索、bgm 浏览与趋势,豆瓣 / bgm 请求缓存端口
- TMDB:流行趋势(日 / 周)、电影 / 剧集榜单、discover 参数映射、分页搜索;列表类请求 TTL 6 小时 - 豆瓣 / bgm:列表与搜索请求经 MetaRequestCache 与限流,失败(403 / 风控页)不缓存 - contracts:发现页条目、分页、查询校验与筛选字典;storage.cacheTtl 追加 meta* 字段 - testkit:mock TMDB 加列表接口 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
27514f4985
commit
e46eb0e3c3
13 files changed
+1344
-91
No files matched your search
@@ -26,6 +26,7 @@ export {
|
||||
ReleaseIdentityResult,
|
||||
TaskAccepted,
|
||||
} from "./api/shared.ts";
|
||||
export * from "./discover.ts";
|
||||
export * from "./search.ts";
|
||||
|
||||
// 所有 REST 端点的路径常量与请求/响应 schema 汇总,按领域分在 ./api/*.ts。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { MetadataEnrichOutput } from "../capabilities-io.ts";
|
||||
import { cursorPage, IdParam } from "../common.ts";
|
||||
import * as discover from "../discover.ts";
|
||||
import * as execution from "../execution.ts";
|
||||
import * as identity from "../identity.ts";
|
||||
import * as libraryVersions from "../library-versions.ts";
|
||||
@@ -211,24 +212,41 @@ export const libraryApi = {
|
||||
method: "get",
|
||||
path: "/discover/bangumi-calendar",
|
||||
scope: "read",
|
||||
summary: "bgm.tv 每日放送(周一到周日;缓存 1 小时)",
|
||||
response: search.DiscoverCalendar,
|
||||
summary: "bgm.tv 每日放送(周一到周日;请求缓存 6 小时;带本地状态)",
|
||||
response: discover.DiscoverCalendar,
|
||||
}),
|
||||
discoverHot: ep({
|
||||
discoverTmdb: ep({
|
||||
method: "get",
|
||||
path: "/discover/douban-hot",
|
||||
path: "/discover/tmdb",
|
||||
scope: "read",
|
||||
summary: "豆瓣实时热门电影 / 剧集(缓存 1 小时)",
|
||||
query: search.DiscoverHotQuery,
|
||||
response: search.DiscoverList,
|
||||
summary:
|
||||
"TMDB 发现:流行趋势、热门、正在上映 / 播出、即将上映、高分、按条件筛选、搜索(走 TMDB 请求缓存;带本地状态)",
|
||||
query: discover.DiscoverTmdbQuery,
|
||||
response: discover.DiscoverPage,
|
||||
}),
|
||||
discoverDouban: ep({
|
||||
method: "get",
|
||||
path: "/discover/douban",
|
||||
scope: "read",
|
||||
summary: "豆瓣发现:榜单、标签、筛选、搜索(走豆瓣请求缓存,失败不缓存)",
|
||||
query: discover.DiscoverDoubanQuery,
|
||||
response: discover.DiscoverPage,
|
||||
}),
|
||||
discoverBangumi: ep({
|
||||
method: "get",
|
||||
path: "/discover/bangumi",
|
||||
scope: "read",
|
||||
summary: "Bangumi 发现:热门趋势、排行、最新、搜索(走 bgm 请求缓存,失败不缓存)",
|
||||
query: discover.DiscoverBangumiQuery,
|
||||
response: discover.DiscoverPage,
|
||||
}),
|
||||
discoverMatch: ep({
|
||||
method: "post",
|
||||
path: "/discover/tmdb-match",
|
||||
scope: "read",
|
||||
summary: "豆瓣 / bgm 条目 → TMDB 作品(名称 + 年份匹配,对照 MP 投影);确定的给 match,否则给候选",
|
||||
body: search.DiscoverMatchRequest,
|
||||
response: search.DiscoverMatchResult,
|
||||
summary: "豆瓣 / bgm 条目 → TMDB 作品(名称 + 年份匹配,对照 MP 投影);确定的给 match 并记住,否则给候选",
|
||||
body: discover.DiscoverMatchRequest,
|
||||
response: discover.DiscoverMatchResult,
|
||||
}),
|
||||
tmdbSeason: ep({
|
||||
method: "get",
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// ── 发现(对照 MP 推荐 / 探索,docs/porting/moviepilot-discover.md) ──
|
||||
// 来源:TMDB(流行趋势、热门、正在上映 / 播出、即将上映、高分、按条件筛选、搜索)、豆瓣(榜单、标签、筛选、搜索)、
|
||||
// Bangumi(每日放送、热门趋势、排行 / 最新、搜索)。豆瓣 / bgm 条目点开时按名称 + 年份匹配 TMDB 作品。
|
||||
// 下面的字典是界面筛选器与服务端校验共用的唯一来源。
|
||||
|
||||
export const DiscoverSource = z.enum(["tmdb", "douban", "bangumi"]);
|
||||
export type DiscoverSource = z.infer<typeof DiscoverSource>;
|
||||
|
||||
/** TMDB 风格(genre id → 中文名,对照 MP TheMovieDbView 的字典;不另请求 /genre/list) */
|
||||
export const TMDB_MOVIE_GENRES: Record<string, string> = {
|
||||
"28": "动作",
|
||||
"12": "冒险",
|
||||
"16": "动画",
|
||||
"35": "喜剧",
|
||||
"80": "犯罪",
|
||||
"99": "纪录",
|
||||
"18": "剧情",
|
||||
"10751": "家庭",
|
||||
"14": "奇幻",
|
||||
"36": "历史",
|
||||
"27": "恐怖",
|
||||
"10402": "音乐",
|
||||
"9648": "悬疑",
|
||||
"10749": "爱情",
|
||||
"878": "科幻",
|
||||
"10770": "电视电影",
|
||||
"53": "惊悚",
|
||||
"10752": "战争",
|
||||
"37": "西部",
|
||||
};
|
||||
export const TMDB_TV_GENRES: Record<string, string> = {
|
||||
"10759": "动作冒险",
|
||||
"16": "动画",
|
||||
"35": "喜剧",
|
||||
"80": "犯罪",
|
||||
"99": "纪录",
|
||||
"18": "剧情",
|
||||
"10751": "家庭",
|
||||
"10762": "儿童",
|
||||
"9648": "悬疑",
|
||||
"10763": "新闻",
|
||||
"10764": "真人秀",
|
||||
"10765": "科幻奇幻",
|
||||
"10766": "肥皂剧",
|
||||
"10767": "脱口秀",
|
||||
"10768": "战争政治",
|
||||
"37": "西部",
|
||||
};
|
||||
export const tmdbGenres = (type: "movie" | "tv") => (type === "movie" ? TMDB_MOVIE_GENRES : TMDB_TV_GENRES);
|
||||
|
||||
/** 原始语言(with_original_language) */
|
||||
export const TMDB_LANGUAGES: Record<string, string> = {
|
||||
zh: "中文",
|
||||
en: "英语",
|
||||
ja: "日语",
|
||||
ko: "韩语",
|
||||
fr: "法语",
|
||||
de: "德语",
|
||||
es: "西班牙语",
|
||||
it: "意大利语",
|
||||
ru: "俄语",
|
||||
pt: "葡萄牙语",
|
||||
th: "泰语",
|
||||
hi: "印地语",
|
||||
};
|
||||
|
||||
/** 出品地区(with_origin_country,ISO 3166-1) */
|
||||
export const TMDB_REGIONS: Record<string, string> = {
|
||||
CN: "中国大陆",
|
||||
HK: "中国香港",
|
||||
TW: "中国台湾",
|
||||
US: "美国",
|
||||
GB: "英国",
|
||||
JP: "日本",
|
||||
KR: "韩国",
|
||||
FR: "法国",
|
||||
DE: "德国",
|
||||
IN: "印度",
|
||||
TH: "泰国",
|
||||
ES: "西班牙",
|
||||
};
|
||||
|
||||
export const TmdbDiscoverSort = z.enum(["popularity", "date", "rating", "votes"]);
|
||||
export type TmdbDiscoverSort = z.infer<typeof TmdbDiscoverSort>;
|
||||
export const TMDB_SORTS: Record<TmdbDiscoverSort, string> = {
|
||||
popularity: "热度",
|
||||
date: "最新",
|
||||
rating: "评分",
|
||||
votes: "评分人数",
|
||||
};
|
||||
|
||||
/** TMDB 列表:trending 流行趋势;popular 热门;now_playing 正在上映;upcoming 即将上映;on_the_air 正在播出;airing_today 今日播出;top_rated 高分;discover 按条件筛选;search 关键词 */
|
||||
export const TmdbDiscoverList = z.enum([
|
||||
"trending",
|
||||
"popular",
|
||||
"now_playing",
|
||||
"upcoming",
|
||||
"on_the_air",
|
||||
"airing_today",
|
||||
"top_rated",
|
||||
"discover",
|
||||
"search",
|
||||
]);
|
||||
export type TmdbDiscoverList = z.infer<typeof TmdbDiscoverList>;
|
||||
/** 只对一种类型有的列表 */
|
||||
export const TMDB_LIST_TYPES: Partial<Record<TmdbDiscoverList, "movie" | "tv">> = {
|
||||
now_playing: "movie",
|
||||
upcoming: "movie",
|
||||
on_the_air: "tv",
|
||||
airing_today: "tv",
|
||||
};
|
||||
|
||||
/** 年份:单年 2024 或年代 2010s */
|
||||
export const DISCOVER_YEAR = /^(19|20)\d{2}s?$/;
|
||||
|
||||
/** 豆瓣榜单(rexxar subject_collection;2026-10-06 协调者实测可用的 id) */
|
||||
export const DOUBAN_COLLECTIONS: Record<string, { label: string; kind: "movie" | "tv" }> = {
|
||||
movie_real_time_hotest: { label: "实时热门电影", kind: "movie" },
|
||||
tv_real_time_hotest: { label: "实时热门剧集", kind: "tv" },
|
||||
movie_showing: { label: "正在热映", kind: "movie" },
|
||||
movie_hot_gaia: { label: "热门电影", kind: "movie" },
|
||||
movie_soon: { label: "即将上映", kind: "movie" },
|
||||
movie_top250: { label: "电影 Top250", kind: "movie" },
|
||||
tv_hot: { label: "热门剧集", kind: "tv" },
|
||||
tv_domestic: { label: "国产剧", kind: "tv" },
|
||||
tv_american: { label: "欧美剧", kind: "tv" },
|
||||
tv_japanese: { label: "日剧", kind: "tv" },
|
||||
tv_korean: { label: "韩剧", kind: "tv" },
|
||||
tv_animation: { label: "动画", kind: "tv" },
|
||||
show_hot: { label: "综艺", kind: "tv" },
|
||||
tv_chinese_best_weekly: { label: "华语口碑剧集榜", kind: "tv" },
|
||||
tv_global_best_weekly: { label: "全球口碑剧集榜", kind: "tv" },
|
||||
};
|
||||
|
||||
/** 豆瓣标签(movie.douban.com/j/search_subjects 的 tag) */
|
||||
export const DOUBAN_TAGS: Record<"movie" | "tv", string[]> = {
|
||||
movie: ["热门", "最新", "豆瓣高分", "冷门佳片", "华语", "欧美", "韩国", "日本"],
|
||||
tv: ["热门", "国产剧", "美剧", "英剧", "韩剧", "日剧", "港剧", "日本动画", "综艺", "纪录片"],
|
||||
};
|
||||
|
||||
/** 豆瓣筛选(rexxar movie|tv/recommend,对照 MP DoubanView) */
|
||||
export const DoubanExploreSort = z.enum(["U", "R", "T", "S"]);
|
||||
export type DoubanExploreSort = z.infer<typeof DoubanExploreSort>;
|
||||
export const DOUBAN_SORTS: Record<DoubanExploreSort, string> = { U: "综合", T: "近期热度", R: "首映时间", S: "高分" };
|
||||
export const DOUBAN_GENRES = [
|
||||
"喜剧",
|
||||
"爱情",
|
||||
"动作",
|
||||
"科幻",
|
||||
"动画",
|
||||
"悬疑",
|
||||
"犯罪",
|
||||
"惊悚",
|
||||
"冒险",
|
||||
"音乐",
|
||||
"历史",
|
||||
"奇幻",
|
||||
"恐怖",
|
||||
"战争",
|
||||
"传记",
|
||||
"歌舞",
|
||||
"武侠",
|
||||
"灾难",
|
||||
"西部",
|
||||
"纪录片",
|
||||
"短片",
|
||||
];
|
||||
export const DOUBAN_REGIONS = [
|
||||
"华语",
|
||||
"欧美",
|
||||
"韩国",
|
||||
"日本",
|
||||
"中国大陆",
|
||||
"美国",
|
||||
"中国香港",
|
||||
"中国台湾",
|
||||
"英国",
|
||||
"法国",
|
||||
"德国",
|
||||
"意大利",
|
||||
"西班牙",
|
||||
"印度",
|
||||
"泰国",
|
||||
];
|
||||
/** 年代或单年:2020年代 / 90年代 / 2024 */
|
||||
export const DOUBAN_YEAR = /^((19|20)\d{2}|(19|20)\d0年代|[6-9]0年代)$/;
|
||||
|
||||
export const BangumiDiscoverList = z.enum(["trends", "rank", "latest", "search"]);
|
||||
export type BangumiDiscoverList = z.infer<typeof BangumiDiscoverList>;
|
||||
|
||||
export const DISCOVER_PAGE_SIZE = 20;
|
||||
|
||||
// ── 条目与分页 ──
|
||||
|
||||
/** 本地状态:TMDB 身份对应的本地作品(豆瓣 / bgm 条目只有匹配过才知道) */
|
||||
export const DiscoverLocal = z.object({
|
||||
entityId: z.string(),
|
||||
subscribed: z.boolean(),
|
||||
versionCount: z.number().int(),
|
||||
downloadingCount: z.number().int(),
|
||||
});
|
||||
export type DiscoverLocal = z.infer<typeof DiscoverLocal>;
|
||||
|
||||
export const DiscoverTmdbRef = z.object({ type: z.enum(["movie", "tv"]), id: z.string() });
|
||||
export type DiscoverTmdbRef = z.infer<typeof DiscoverTmdbRef>;
|
||||
|
||||
export const DiscoverItem = z.object({
|
||||
source: DiscoverSource,
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
aliases: z.array(z.string()),
|
||||
year: z.number().int().nullable(),
|
||||
kind: z.enum(["movie", "tv"]).nullable(),
|
||||
/** 豆瓣 / bgm 封面完整地址;TMDB 用 posterPath */
|
||||
cover: z.string().nullable(),
|
||||
rating: z.number().nullable(),
|
||||
date: z.string().nullable(),
|
||||
episodes: z.number().int().nullable(),
|
||||
url: z.string(),
|
||||
genres: z.array(z.string()).default([]),
|
||||
posterPath: z.string().nullable().default(null),
|
||||
/** TMDB 条目自身;豆瓣 / bgm 条目匹配过且确定时给出 */
|
||||
tmdb: DiscoverTmdbRef.nullable().default(null),
|
||||
local: DiscoverLocal.nullable().default(null),
|
||||
});
|
||||
export type DiscoverItem = z.infer<typeof DiscoverItem>;
|
||||
|
||||
export const DiscoverPage = z.object({
|
||||
items: z.array(DiscoverItem),
|
||||
page: z.number().int(),
|
||||
hasMore: z.boolean(),
|
||||
});
|
||||
export type DiscoverPage = z.infer<typeof DiscoverPage>;
|
||||
|
||||
export const DiscoverCalendar = z.object({
|
||||
days: z.array(z.object({ weekday: z.number().int(), label: z.string(), items: z.array(DiscoverItem) })),
|
||||
});
|
||||
export type DiscoverCalendar = z.infer<typeof DiscoverCalendar>;
|
||||
|
||||
// ── 查询参数 ──
|
||||
|
||||
const Page = z.coerce.number().int().min(1).max(500).default(1);
|
||||
const Q = z.string().trim().min(1).max(100);
|
||||
|
||||
const issue = (ctx: z.RefinementCtx, path: string, message: string) =>
|
||||
ctx.addIssue({ code: "custom", path: [path], message });
|
||||
|
||||
export const DiscoverTmdbQuery = z
|
||||
.object({
|
||||
list: TmdbDiscoverList.default("trending"),
|
||||
type: z.enum(["all", "movie", "tv"]).default("all"),
|
||||
/** 只用于 trending */
|
||||
window: z.enum(["day", "week"]).default("week"),
|
||||
sort: TmdbDiscoverSort.optional(),
|
||||
genre: z
|
||||
.string()
|
||||
.regex(/^\d{1,6}$/)
|
||||
.optional(),
|
||||
language: z
|
||||
.string()
|
||||
.regex(/^[a-z]{2}$/)
|
||||
.optional(),
|
||||
region: z
|
||||
.string()
|
||||
.regex(/^[A-Z]{2}$/)
|
||||
.optional(),
|
||||
year: z.string().regex(DISCOVER_YEAR).optional(),
|
||||
minRating: z.coerce.number().min(0).max(10).optional(),
|
||||
q: Q.optional(),
|
||||
page: Page,
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
const only = TMDB_LIST_TYPES[v.list];
|
||||
if (only && v.type !== only) issue(ctx, "type", `${v.list} 只有${only === "movie" ? "电影" : "剧集"}`);
|
||||
if (v.type === "all" && !["trending", "search"].includes(v.list) && !only)
|
||||
issue(ctx, "type", "这个列表要选电影或剧集");
|
||||
const filters = (["sort", "genre", "language", "region", "year", "minRating"] as const).filter(
|
||||
(k) => v[k] !== undefined,
|
||||
);
|
||||
if (v.list !== "discover" && filters.length) issue(ctx, filters[0]!, "筛选条件只用于 list=discover");
|
||||
if (v.genre && v.type !== "all" && !(v.genre in tmdbGenres(v.type))) issue(ctx, "genre", "没有这个风格");
|
||||
if (v.list === "search" && !v.q) issue(ctx, "q", "搜索要给关键词");
|
||||
if (v.list !== "search" && v.q) issue(ctx, "q", "关键词只用于 list=search");
|
||||
});
|
||||
export type DiscoverTmdbQuery = z.infer<typeof DiscoverTmdbQuery>;
|
||||
|
||||
export const DiscoverDoubanQuery = z
|
||||
.object({
|
||||
/** 榜单 id、tag(标签)、explore(筛选)、search(关键词) */
|
||||
list: z.string().max(40).default("movie_real_time_hotest"),
|
||||
type: z.enum(["movie", "tv"]).default("movie"),
|
||||
tag: z.string().max(20).optional(),
|
||||
sort: DoubanExploreSort.optional(),
|
||||
genre: z.string().max(10).optional(),
|
||||
region: z.string().max(10).optional(),
|
||||
year: z.string().regex(DOUBAN_YEAR).optional(),
|
||||
q: Q.optional(),
|
||||
page: z.coerce.number().int().min(1).max(50).default(1),
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
const special = ["tag", "explore", "search"];
|
||||
if (!special.includes(v.list) && !(v.list in DOUBAN_COLLECTIONS)) issue(ctx, "list", "没有这个豆瓣榜单");
|
||||
if (v.list === "tag" && !(v.tag && DOUBAN_TAGS[v.type].includes(v.tag))) issue(ctx, "tag", "没有这个标签");
|
||||
if (v.list !== "tag" && v.tag) issue(ctx, "tag", "标签只用于 list=tag");
|
||||
const filters = (["sort", "genre", "region", "year"] as const).filter((k) => v[k] !== undefined);
|
||||
if (v.list !== "explore" && filters.length) issue(ctx, filters[0]!, "筛选条件只用于 list=explore");
|
||||
if (v.genre && !DOUBAN_GENRES.includes(v.genre)) issue(ctx, "genre", "没有这个风格");
|
||||
if (v.region && !DOUBAN_REGIONS.includes(v.region)) issue(ctx, "region", "没有这个地区");
|
||||
if (v.list === "search" && !v.q) issue(ctx, "q", "搜索要给关键词");
|
||||
if (v.list !== "search" && v.q) issue(ctx, "q", "关键词只用于 list=search");
|
||||
});
|
||||
export type DiscoverDoubanQuery = z.infer<typeof DiscoverDoubanQuery>;
|
||||
|
||||
export const DiscoverBangumiQuery = z
|
||||
.object({
|
||||
list: BangumiDiscoverList.default("trends"),
|
||||
year: z
|
||||
.string()
|
||||
.regex(/^(19|20)\d{2}$/)
|
||||
.optional(),
|
||||
q: Q.optional(),
|
||||
page: z.coerce.number().int().min(1).max(100).default(1),
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
if (v.year && !["rank", "latest"].includes(v.list)) issue(ctx, "year", "年份只用于排行与最新");
|
||||
if (v.list === "search" && !v.q) issue(ctx, "q", "搜索要给关键词");
|
||||
if (v.list !== "search" && v.q) issue(ctx, "q", "关键词只用于 list=search");
|
||||
});
|
||||
export type DiscoverBangumiQuery = z.infer<typeof DiscoverBangumiQuery>;
|
||||
|
||||
// ── 匹配 TMDB ──
|
||||
|
||||
export const DiscoverMatchRequest = z.object({
|
||||
source: z.enum(["douban", "bangumi"]),
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
aliases: z.array(z.string()),
|
||||
year: z.number().int().nullable(),
|
||||
kind: z.enum(["movie", "tv"]).nullable(),
|
||||
});
|
||||
export type DiscoverMatchRequest = z.infer<typeof DiscoverMatchRequest>;
|
||||
|
||||
export const DiscoverMatchCandidate = z.object({
|
||||
type: z.enum(["movie", "tv"]),
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
year: z.number().int().nullable(),
|
||||
posterPath: z.string().nullable(),
|
||||
});
|
||||
export type DiscoverMatchCandidate = z.infer<typeof DiscoverMatchCandidate>;
|
||||
export const DiscoverMatchResult = z.object({
|
||||
/** 能确定的 TMDB 作品;不确定为 null,看 candidates */
|
||||
match: DiscoverMatchCandidate.nullable(),
|
||||
candidates: z.array(DiscoverMatchCandidate),
|
||||
});
|
||||
export type DiscoverMatchResult = z.infer<typeof DiscoverMatchResult>;
|
||||
@@ -475,55 +475,3 @@ export const TmdbSeasonDetailView = z.object({
|
||||
episodes: z.array(MediaEpisodeInfo),
|
||||
});
|
||||
export type TmdbSeasonDetailView = z.infer<typeof TmdbSeasonDetailView>;
|
||||
|
||||
// ── 发现:bgm.tv 每日放送、豆瓣热门(辅助信息源,点开后匹配 TMDB 作品) ──
|
||||
|
||||
export const DiscoverItem = z.object({
|
||||
source: z.enum(["bangumi", "douban"]),
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
aliases: z.array(z.string()),
|
||||
year: z.number().int().nullable(),
|
||||
kind: z.enum(["movie", "tv"]).nullable(),
|
||||
cover: z.string().nullable(),
|
||||
rating: z.number().nullable(),
|
||||
date: z.string().nullable(),
|
||||
episodes: z.number().int().nullable(),
|
||||
url: z.string(),
|
||||
});
|
||||
export type DiscoverItem = z.infer<typeof DiscoverItem>;
|
||||
|
||||
export const DiscoverCalendar = z.object({
|
||||
days: z.array(z.object({ weekday: z.number().int(), label: z.string(), items: z.array(DiscoverItem) })),
|
||||
});
|
||||
export type DiscoverCalendar = z.infer<typeof DiscoverCalendar>;
|
||||
|
||||
export const DiscoverHotQuery = z.object({ kind: z.enum(["movie", "tv"]).default("movie") });
|
||||
export type DiscoverHotQuery = z.infer<typeof DiscoverHotQuery>;
|
||||
|
||||
export const DiscoverList = z.object({ items: z.array(DiscoverItem) });
|
||||
export type DiscoverList = z.infer<typeof DiscoverList>;
|
||||
|
||||
export const DiscoverMatchRequest = DiscoverItem.pick({
|
||||
source: true,
|
||||
id: true,
|
||||
title: true,
|
||||
aliases: true,
|
||||
year: true,
|
||||
kind: true,
|
||||
});
|
||||
export type DiscoverMatchRequest = z.infer<typeof DiscoverMatchRequest>;
|
||||
|
||||
export const DiscoverMatchCandidate = z.object({
|
||||
type: z.enum(["movie", "tv"]),
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
year: z.number().int().nullable(),
|
||||
posterPath: z.string().nullable(),
|
||||
});
|
||||
export const DiscoverMatchResult = z.object({
|
||||
/** 能确定的 TMDB 作品;不确定为 null,看 candidates */
|
||||
match: DiscoverMatchCandidate.nullable(),
|
||||
candidates: z.array(DiscoverMatchCandidate),
|
||||
});
|
||||
export type DiscoverMatchResult = z.infer<typeof DiscoverMatchResult>;
|
||||
@@ -572,6 +572,12 @@ export const StorageCacheTtl = z.object({
|
||||
siteDetailDays: Days,
|
||||
/** 网页搜索结果 */
|
||||
webSearchDays: Days,
|
||||
/** 豆瓣 / bgm 搜索请求(kv_cache douban.request / bangumi.request;条目详情走 douban.subject / bangumi.subject,见 docs/porting/moviepilot-discover.md) */
|
||||
metaSearchDays: Days,
|
||||
/** 豆瓣 / bgm 榜单、热门、每日放送等列表请求(小时) */
|
||||
metaListHours: z.number().int().min(1).max(720),
|
||||
/** 豆瓣 / bgm 空结果(小时,对照 MP empty_ttl) */
|
||||
metaEmptyHours: z.number().int().min(1).max(720),
|
||||
});
|
||||
export type StorageCacheTtl = z.infer<typeof StorageCacheTtl>;
|
||||
|
||||
@@ -594,7 +600,16 @@ export const DEFAULT_STORAGE_SETTINGS: StorageSettings = {
|
||||
capabilityStats: 90,
|
||||
identityPoolArchive: 30,
|
||||
},
|
||||
cacheTtl: { tmdbSearchDays: 7, tmdbDetailDays: 30, tmdbMissDays: 1, siteDetailDays: 7, webSearchDays: 1 },
|
||||
cacheTtl: {
|
||||
tmdbSearchDays: 7,
|
||||
tmdbDetailDays: 30,
|
||||
tmdbMissDays: 1,
|
||||
siteDetailDays: 7,
|
||||
webSearchDays: 1,
|
||||
metaSearchDays: 1,
|
||||
metaListHours: 6,
|
||||
metaEmptyHours: 1,
|
||||
},
|
||||
};
|
||||
|
||||
/** 存储设置与缺省逐层合并(旧库里缺子字段时补齐) */
|
||||
|
||||
Reference in new issue
Block a user