feat(sources): MP-aligned TMDB recall, alias registration guard, re-resolve on retitle (ID-2)
recallTmdbCandidates searches cn then en names in the preferred language by type and fetches details for a bounded set of candidates (MP alias fallback); translations become aliases; bindAuto no longer registers generic or alias-only site titles; snapshot/feed title changes reparse and force a re-resolve that unbinds when it can no longer confirm. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f6ec993b5f
commit
387f7931e0
9 files changed
+269
-39
No files matched your search
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* ID-2:站点资源标题更新(RSS 带分类前缀 / 内嵌描述的标题 → 列表标题)后按新标题重新解析并重新识别,
|
||||
* 不沿用旧解析;已识别的资源也不短路。
|
||||
*/
|
||||
import type { SiteRelease } from "@umt/adapters";
|
||||
import { PARSER_VERSION } from "@umt/domain";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { upsertSiteRelease } from "../../src/modules/sources-identity/service/snapshot.ts";
|
||||
import { sourcesOutboxRoutes } from "../../src/modules/sources-identity/tasks.ts";
|
||||
import { createHarness, type Harness, pgAvailable, seedSite } from "./harness.ts";
|
||||
|
||||
const up = await pgAvailable();
|
||||
|
||||
const rel = (title: string, subtitle?: string): SiteRelease => ({
|
||||
siteId: "x",
|
||||
siteReleaseId: "9001",
|
||||
title,
|
||||
...(subtitle ? { subtitle } : {}),
|
||||
category: { id: "402", name: "剧集" },
|
||||
sizeBytes: null,
|
||||
seeders: null,
|
||||
leechers: null,
|
||||
snatched: null,
|
||||
promotion: { type: "none", downloadFactor: 1, uploadFactor: 1, expiresAt: null } as SiteRelease["promotion"],
|
||||
hr: false,
|
||||
uploadedAt: null,
|
||||
detailUrl: "https://hhan.example/details.php?id=9001",
|
||||
tags: [],
|
||||
raw: {},
|
||||
observedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
describe.skipIf(!up)("标题更新后重新解析 + 重新识别", () => {
|
||||
let h: Harness;
|
||||
let siteId: string;
|
||||
beforeAll(async () => {
|
||||
h = await createHarness("retitle");
|
||||
siteId = (await seedSite(h, { baseUrl: "https://hhan.example/" })).site.id;
|
||||
});
|
||||
afterAll(async () => {
|
||||
await h?.close();
|
||||
});
|
||||
|
||||
it("RSS 标题 → 列表标题:parsed 按新标题重算,发 release.observed(isNew=false)并强制重新识别", async () => {
|
||||
const rss =
|
||||
"[电视剧]Irreplaceable S01 2026 1080p YK WEB-DL H.264 DTS5.1-HHWEB[无可替代 | 第03集 | 1080p | 类型: 剧情][510.37 MB]";
|
||||
const first = await h.db.transaction().execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(rss)));
|
||||
expect(first.inserted).toBe(true);
|
||||
let row = await h.db.selectFrom("source_release").selectAll().where("id", "=", first.id).executeTakeFirstOrThrow();
|
||||
// 分类前缀不进片名;内嵌描述里的中文名补作中文名
|
||||
expect(row.parsed).toMatchObject({ titleCn: "无可替代", titleEn: "Irreplaceable", category: "电视剧" });
|
||||
|
||||
const listTitle = "Irreplaceable S01 2026 1080p YK WEB-DL H.264 DTS5.1-HHWEB";
|
||||
const again = await h.db
|
||||
.transaction()
|
||||
.execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(listTitle, "无可替代 | 第04集 | 1080p")));
|
||||
expect(again).toEqual({ id: first.id, inserted: false });
|
||||
row = await h.db.selectFrom("source_release").selectAll().where("id", "=", first.id).executeTakeFirstOrThrow();
|
||||
expect(row.title).toBe(listTitle);
|
||||
expect(row.parser_version).toBe(String(PARSER_VERSION));
|
||||
expect(row.parsed).toMatchObject({ titleCn: "无可替代", episodes: { start: 4, end: 4 } });
|
||||
|
||||
const events = (await h.outbox("release.observed")).filter((e) => e.payload.sourceReleaseId === first.id);
|
||||
expect(events.map((e) => e.payload.isNew)).toEqual([true, false]);
|
||||
const route = sourcesOutboxRoutes["release.observed"]!({ payload: events[1]!.payload } as never);
|
||||
expect(route).toMatchObject({ task: "resolveObservation", input: { force: true, trigger: "observed" } });
|
||||
|
||||
// 同标题再次快照:不再发重新识别
|
||||
await h.db
|
||||
.transaction()
|
||||
.execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(listTitle, "无可替代 | 第04集 | 1080p")));
|
||||
const after = (await h.outbox("release.observed")).filter((e) => e.payload.sourceReleaseId === first.id);
|
||||
expect(after).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user