Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei # Conflicts: # packages/adapters/src/tmdb/index.ts
261 lines
9.6 KiB
TypeScript
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;
|