feat(server): 发现页接口——TMDB / 豆瓣 / Bangumi 列表、本地状态、确定匹配记忆

- GET /discover/{tmdb,douban,bangumi},bgm 趋势页在沙箱任务 bangumi.trends 里解析
- 豆瓣 / bgm 每个列表 / 搜索请求走 douban.request / bangumi.request 缓存(TTL 取 storage.cacheTtl),每源限流
- 条目按 TMDB 身份标已订阅 / 媒体库已有 / 下载中;豆瓣 / bgm 条目确定匹配后记在 discover.match

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 18:04:43 +08:00
1 parent e46eb0e3c3
commit 9a154bc249
5 files changed
+576 -27

No files matched your search

+20 -4
View File
@@ -11,8 +11,10 @@ import {
type ApiScope,
apiResponse,
DEFAULT_SETTINGS,
type DiscoverHotQuery,
type DiscoverBangumiQuery,
type DiscoverDoubanQuery,
type DiscoverMatchRequest,
type DiscoverTmdbQuery,
type EndpointDef,
type InventoryEntry,
type InventoryResponse,
@@ -40,7 +42,7 @@ import { metadataLanguages, metadataTextOptions } from "../sources-identity/serv
import { upsertSiteRelease } from "../sources-identity/service/snapshot.ts";
import type { ModuleApp, ServerContext } from "../types.ts";
import { matchActions } from "./actions.ts";
import { bangumiCalendar, doubanHot, matchTmdb } from "./discover.ts";
import { annotate, bangumiCalendar, bangumiList, doubanList, matchTmdb, tmdbList } from "./discover.ts";
import { InternalDispatcher } from "./dispatch.ts";
import { localEntities, raceAbort, searchLocalMedia, TmdbSearch, withInventory } from "./media.ts";
import { decorate, facets, quickFilter, ReleaseFanout, sortReleases } from "./releases.ts";
@@ -383,8 +385,22 @@ export function registerSearchRoutes(app: ModuleApp, ctx: ServerContext, d: Sear
});
});
route(app, Api.search.discoverCalendar, () => bangumiCalendar(d.runtime()));
route(app, Api.search.discoverHot, ({ query }) => doubanHot(d.runtime(), (query as DiscoverHotQuery).kind));
route(app, Api.search.discoverCalendar, ({ c }) => bangumiCalendar(d.runtime(), inventory(c)));
const withLocal =
(c: Context<AuthEnv>) =>
async <P extends { items: Parameters<typeof annotate>[1] }>(p: P): Promise<P> => {
await annotate(d.runtime(), p.items, inventory(c));
return p;
};
route(app, Api.search.discoverTmdb, async ({ query, c }) =>
withLocal(c)(await tmdbList(d.runtime(), query as DiscoverTmdbQuery)),
);
route(app, Api.search.discoverDouban, async ({ query, c }) =>
withLocal(c)(await doubanList(d.runtime(), query as DiscoverDoubanQuery)),
);
route(app, Api.search.discoverBangumi, async ({ query, c }) =>
withLocal(c)(await bangumiList(d.runtime(), query as DiscoverBangumiQuery)),
);
route(app, Api.search.discoverMatch, ({ body }) => matchTmdb(d.runtime(), body as DiscoverMatchRequest));
route(app, Api.search.tmdbDetail, async ({ params, query, c }) => {