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"; import * as organization from "../organization.ts"; import * as scrape from "../scrape.ts"; import * as search from "../search.ts"; import { ep, ReleaseIdentityResult, TaskAccepted } from "./shared.ts"; export const libraryApi = { // ── 待识别池 ── identityPool: { list: ep({ method: "get", path: "/identity-pool", scope: "read", summary: "未识别资源(可按原始标题检索;候选不建正式作品)", query: identity.IdentityPoolQuery, response: cursorPage(identity.IdentityPoolItem), }), assign: ep({ method: "post", path: "/identity-pool/{id}/assign", scope: "organize.execute", summary: "为未识别资源手动指定候选 / 本地作品 / 外部身份", params: IdParam, body: identity.ReleaseIdentityAssign, response: ReleaseIdentityResult, }), dismiss: ep({ method: "post", path: "/identity-pool/{id}/dismiss", scope: "organize.execute", summary: "忽略未识别资源(不再自动复查)", params: IdParam, body: z.object({ note: z.string().max(2000).optional() }), response: identity.IdentityCandidate, }), retry: ep({ method: "post", path: "/identity-pool/{id}/retry", scope: "organize.execute", summary: "立即重新识别", params: IdParam, response: TaskAccepted, }), // META-1 元数据补全:先看建议(metadata.enrich,不改作品),再在控制台应用 enrich: ep({ method: "post", path: "/identity-pool/{id}/enrich", scope: "organize.execute", summary: "元数据补全建议(网页搜索 + 白名单网页 + 模型抽取;不改作品;同一标题 7 天内返回上次结果)", params: IdParam, body: identity.EnrichRequest, response: MetadataEnrichOutput, }), enrichApply: ep({ method: "post", path: "/identity-pool/{id}/enrich/apply", scope: "admin", auth: "session", summary: "应用元数据补全建议:建本地临时作品(或并入同名作品)并关联未识别资源(只接受控制台管理员会话)", params: IdParam, body: identity.EnrichApplyInput, response: identity.EnrichApplyResult, }), // META-2 补全命中率(按来源类别) enrichStats: ep({ method: "get", path: "/identity/enrich-stats", scope: "read", summary: "元数据补全命中率(最近 N 天,按来源类别:百科、平台、豆瓣摘要、其它网站摘要)", query: identity.EnrichStatsQuery, response: identity.EnrichStats, }), }, // ── 媒体库 / 库存 / 首页 ── library: { list: ep({ method: "get", path: "/library", scope: "read", summary: "媒体库列表(已登记资产或正在追踪的作品)", query: organization.LibraryListQuery, response: cursorPage(organization.LibraryItem), }), get: ep({ method: "get", path: "/library/{id}", scope: "read", summary: "作品详情:实际路径、身份映射、元数据来源、各收录版本、候选拒绝理由、下载历史与校验结果", params: IdParam, response: organization.LibraryDetail, }), versions: ep({ method: "get", path: "/library/{id}/versions", scope: "read", summary: "作品的已整理版本:每季每集的本地文件版本与规格、季 / 作品汇总、与版本目标的比对", params: IdParam, query: libraryVersions.LibraryVersionsQuery, response: libraryVersions.LibraryVersions, }), scrape: ep({ method: "post", path: "/library/{id}/scrape", scope: "organize.execute", summary: "刮削:把作品的 NFO 与图片写进媒体库目录(后台任务 scrapeMedia;覆盖方式缺省按设置)", params: IdParam, body: scrape.LibraryScrapeRequest, response: scrape.LibraryScrapeResult, }), reconcile: ep({ method: "post", path: "/library/reconcile", scope: "admin", summary: "立即做一轮资产对账(库根哨兵 + 增量 stat;不删记录、不补下载)", body: z.object({ libraryRootId: z.uuid().optional() }), response: z.object({ runId: z.uuid(), stats: z.record(z.string(), z.unknown()) }), }), }, inventory: { overlay: ep({ method: "get", path: "/inventory", scope: "read", summary: "库存叠加(站点工作台):按作品返回已有版本 / 收录版本缺口 / 下载中", query: organization.InventoryQuery, response: organization.InventoryResponse, }), mediaServers: ep({ method: "get", path: "/inventory/media-servers", scope: "read", summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)", query: organization.MediaServerInventoryQuery, response: organization.MediaServerInventory, }), }, dashboard: { get: ep({ method: "get", path: "/dashboard", scope: "read", summary: "首页聚合:自动化概览/当前工作/需要我处理/缓存与保留/来源健康/最近产出", response: organization.Dashboard, }), }, attention: { summary: ep({ method: "get", path: "/attention", scope: "read", summary: "需要关注的汇总:整理记录需要处理的条数、等你批准的智能体操作、系统健康问题", response: organization.AttentionSummary, }), }, downloadCache: { cleanup: ep({ method: "post", path: "/download-cache/cleanup", scope: "admin", summary: "立即按阈值清理下载缓存(只清已核验整理且满足保留/H&R 的纳管任务)", body: z.object({ downloaderInstanceId: z.uuid().optional() }), response: z.object({ report: z.record(z.string(), z.unknown()) }), }), }, // ── 全局搜索(W9-G):顶栏搜索 / 命令面板 / 资源结果页 ── search: { all: ep({ method: "get", path: "/search", scope: "read", summary: "全局搜索聚合:本地作品 / TMDB 候选 / 跨站资源(15s 超时、并发合并、30s 缓存、库存叠加)/ 页面与操作;每组带 tookMs", query: search.SearchQuery, response: search.SearchResponse, }), releases: ep({ method: "get", path: "/search/releases", scope: "read", summary: "跨站资源结果页:站点/分辨率/来源/大小/免费/H&R 筛选、排序、游标;与 /search 共用缓存", query: search.ReleaseSearchQuery, response: search.ReleaseSearchPage, }), plan: ep({ method: "post", path: "/search/releases/{siteId}/{siteReleaseId}/plan", scope: "organize.execute", summary: "从搜索结果直接下载:定位资源后走 POST /acquisition-plans 同一流程", params: search.SearchReleaseParams, body: search.SearchReleasePlanRequest, response: execution.ManualAcquisitionResult, }), tmdbDetail: ep({ method: "get", path: "/media/tmdb/{type}/{id}", scope: "read", summary: "仅 TMDB 作品的详情:TMDB 信息与季列表、本地库存对比、是否已建档、搜资源的查询词;include=mediaServers 同时查 Emby", params: search.TmdbMediaParams, query: search.TmdbMediaQuery, response: search.TmdbMediaDetail, }), discoverCalendar: ep({ method: "get", path: "/discover/bangumi-calendar", scope: "read", summary: "bgm.tv 每日放送(周一到周日;请求缓存 6 小时;带本地状态)", response: discover.DiscoverCalendar, }), discoverTmdb: ep({ method: "get", path: "/discover/tmdb", scope: "read", 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: discover.DiscoverMatchRequest, response: discover.DiscoverMatchResult, }), tmdbSeason: ep({ method: "get", path: "/media/tmdb/tv/{id}/season/{season}", scope: "read", summary: "仅 TMDB 剧集的季详情与每集信息(标题 / 简介按语言回退链;走 TMDB 请求缓存)", params: search.TmdbSeasonParams, response: search.TmdbSeasonDetailView, }), }, } as const;