Files
UHD_Media_Tool/packages/contracts/src/api/library.ts
T
root b32d5c028a merge: ui2/t-discover
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei

# Conflicts:
#	packages/adapters/src/tmdb/index.ts
2026-10-06 18:34:12 +08:00

261 lines
9.6 KiB
TypeScript

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;