Merge branch 'md1'

# Conflicts:
#	packages/contracts/src/settings.ts
This commit is contained in:
root committed 2026-09-29 17:42:31 +08:00
commit b633eb5379
30 files changed
+3240 -222

No files matched your search

+34 -2
View File
@@ -10,6 +10,7 @@ import {
MediaEntity,
MediaEntityDetail,
type MediaKind,
type UnitMetadataField,
} from "@umt/contracts";
import type { z } from "zod";
import { ApiRequestError, api, type QueryParams } from "~/lib/api.ts";
@@ -144,11 +145,42 @@ export function useUnlockField(id: string) {
});
}
/** 刷新元数据(字段级;用户锁优先) */
/**
* 刷新元数据(字段级;用户锁优先)。full = 拉全部季的每集信息。刷新是后台任务:
* 提交后隔几秒再取一次详情,拿到任务写回的结果。
*/
export function useRefreshMetadata(id: string) {
const done = useInvalidateEntity(id);
return useMutation({
mutationFn: () => api.post<unknown>(`/media-entities/${id}/refresh`),
mutationFn: (opts?: { full?: boolean }) =>
api.post<unknown>(`/media-entities/${id}/refresh${opts?.full ? "?full=1" : ""}`),
onSuccess: () => {
done();
for (const ms of [3_000, 8_000, 20_000]) setTimeout(done, ms);
},
});
}
/** 季 / 集字段覆盖并锁定(MD-1) */
export function useLockUnitField(entityId: string) {
const done = useInvalidateEntity(entityId);
return useMutation({
mutationFn: (v: { unitId: string; field: UnitMetadataField; value: unknown; reason?: string }) =>
api.put<unknown>(`/content-units/${v.unitId}/fields/${v.field}`, {
value: v.value,
lock: true,
reason: v.reason,
}),
onSuccess: done,
});
}
/** 取消季 / 集字段覆盖与锁定 */
export function useUnlockUnitField(entityId: string) {
const done = useInvalidateEntity(entityId);
return useMutation({
mutationFn: (v: { unitId: string; field: UnitMetadataField }) =>
api.del<unknown>(`/content-units/${v.unitId}/fields/${v.field}`),
onSuccess: done,
});
}
+12
View File
@@ -8,6 +8,7 @@ import {
SearchResponse,
type SearchScope,
TmdbMediaDetail,
TmdbSeasonDetailView,
} from "@umt/contracts";
import { api } from "~/lib/api.ts";
import { getParsed, parseOrThrow } from "./library.ts";
@@ -20,6 +21,7 @@ export const searchKeys = {
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,
tmdbSeason: (id: string, season: number) => ["search", "tmdb-season", id, season] as const,
mediaServers: (ref: MediaServerRef) => ["search", "media-servers", ref] as const,
};
@@ -114,6 +116,16 @@ export function useTmdbMedia(type: string | undefined, id: string | undefined) {
});
}
/** 仅 TMDB 剧集的季详情与每集信息(MD-1,季折叠展开时才拉) */
export function useTmdbSeason(id: string | undefined, season: number, enabled: boolean) {
return useQuery({
queryKey: searchKeys.tmdbSeason(id ?? "", season),
enabled: enabled && !!id && /^\d+$/.test(id),
queryFn: ({ signal }) => getParsed(TmdbSeasonDetailView, `/media/tmdb/tv/${id}/season/${season}`, {}, signal),
staleTime: 5 * 60_000,
});
}
export type MediaServerRef = { mediaEntityId: string } | { tmdbType: "movie" | "tv"; tmdbId: string };
/** Emby 库存对比(按本地作品或 TMDB 条目);实例失败 / 超时为 unknown,不等于缺失 */