docs(plan): on-demand site detail page parsing (HHClub details.php)

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-29 15:13:35 +08:00
1 parent 94fd48bff2
commit ac478673bc
39 files changed
+2446 -339

No files matched your search

+39 -4
View File
@@ -1,21 +1,26 @@
import { keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
ManualAcquisitionResult,
MediaServerInventory,
ReleaseSearchPage,
type ReleaseSearchSort,
type SearchReleasePlanRequest,
SearchResponse,
type SearchScope,
TmdbMediaDetail,
} from "@umt/contracts";
import { api } from "~/lib/api.ts";
import { getParsed, parseOrThrow } from "./library.ts";
// 全局搜索(W9-G):命令面板查 media/actions(快);资源结果页走 /search/releases(筛选/排序/游标,与 /search 共用 30s 缓存)。
// 全局搜索(W9-G / SEARCH-2):先查作品(本地 + TMDB,带库存对比,不碰站点);需要时按作品显式搜资源
// /search/releases(站点可选,筛选/排序/游标,(站点, 查询词)30s 缓存)。仅 TMDB 的作品详情 /media/tmdb/{type}/{id}。
export const searchKeys = {
all: ["search"] as const,
aggregate: (q: string, scope: SearchScope, limit: number) => ["search", "aggregate", q, scope, limit] as const,
releases: (f: ReleaseSearchFilters) => ["search", "releases", f] as const,
tmdb: (type: string, id: string, ms: boolean) => ["search", "tmdb", type, id, ms] as const,
mediaServers: (ref: MediaServerRef) => ["search", "media-servers", ref] as const,
};
/** 聚合搜索;q 为空不发请求 */
@@ -31,9 +36,16 @@ export function useSearch(q: string, scope: SearchScope, limit = 8, enabled = tr
}
export type ReleaseSearchFilters = {
q: string;
/** 逗号分隔 */
/** 关键词;按作品搜时省略,由服务端生成查询词 */
q?: string;
/** 按本地作品搜 */
entityId?: string;
/** 按 TMDB 条目搜:movie:157336 */
tmdb?: string;
/** 逗号分隔:只查这些站点 */
sites?: string;
/** 逗号分隔:只筛选结果 */
siteFilter?: string;
resolution?: string;
source?: string;
/** 字节 */
@@ -49,7 +61,7 @@ export type ReleaseSearchFilters = {
export function useReleaseSearch(filters: ReleaseSearchFilters, enabled = true) {
return useInfiniteQuery({
queryKey: searchKeys.releases(filters),
enabled: enabled && filters.q.trim().length > 0,
enabled: enabled && (!!filters.q?.trim() || !!filters.entityId || !!filters.tmdb),
initialPageParam: undefined as string | undefined,
queryFn: ({ pageParam, signal }) =>
getParsed(ReleaseSearchPage, "/search/releases", { ...filters, cursor: pageParam, limit: 50 }, signal),
@@ -91,3 +103,26 @@ export function usePlanFromSearch() {
},
});
}
/** 仅 TMDB 作品的详情(TMDB 信息 + 季 + 本地对比);Emby 对比另走 useMediaServerInventory */
export function useTmdbMedia(type: string | undefined, id: string | undefined) {
return useQuery({
queryKey: searchKeys.tmdb(type ?? "", id ?? "", false),
enabled: (type === "movie" || type === "tv") && !!id && /^\d+$/.test(id),
queryFn: ({ signal }) => getParsed(TmdbMediaDetail, `/media/tmdb/${type}/${id}`, {}, signal),
staleTime: 60_000,
});
}
export type MediaServerRef = { mediaEntityId: string } | { tmdbType: "movie" | "tv"; tmdbId: string };
/** Emby 库存对比(按本地作品或 TMDB 条目);实例失败 / 超时为 unknown,不等于缺失 */
export function useMediaServerInventory(ref: MediaServerRef | null, enabled = true) {
return useQuery({
queryKey: searchKeys.mediaServers(ref ?? { mediaEntityId: "" }),
enabled: enabled && !!ref,
queryFn: ({ signal }) => getParsed(MediaServerInventory, "/inventory/media-servers", ref ?? {}, signal),
staleTime: 5 * 60_000,
retry: false,
});
}