/** ID-2:副标题 / RSS 内嵌描述片名、分类前缀、续作序号、通用词守卫、评分权重与同级取舍 */ import { describe, expect, it } from "vitest"; import { DEFAULT_MATCH_WEIGHTS, type IdentityCandidate, isGenericAliasName, normalizeTitle, parseRelease, queryTitles, resolveIdentity, scoreCandidate, sequelNumbers, } from "../src/index.ts"; const tv = (id: string, titles: string[], year: number, over: Partial = {}): IdentityCandidate => ({ provider: "tmdb", entityType: "tv", externalId: id, titles, year, aliases: [], ...over, }); describe("解析:分类前缀、内嵌描述、副标题片名", () => { it("RSS `[动漫]英文名 … -GRP[中文名 / 别名 | 第N集 | …][体积]`", () => { const p = parseRelease( "[动漫]Martial Master S01 2020 1080p WEB-DL H.264 AAC2.0-HHWEB[武神主宰 / 武神主宰 第一季 / The God of War Dominates | 第685集 | 1080p | 类型: 动画/奇幻/冒险 | 导演: 老鹰][180.86 MB]", ); expect(p).toMatchObject({ category: "动漫", titleCn: "武神主宰", titleEn: "Martial Master", year: 2020, season: 1, episodes: { start: 685, end: 685 }, altTitles: ["武神主宰 第一季", "The God of War Dominates"], }); expect(p.evidence).toContain("subtitle:embedded"); }); it("分类词不当片名:[电视剧] / [音乐] / [综艺]", () => { for (const c of ["电视剧", "音乐", "综艺", "纪录片", "短剧", "游戏", "软件", "学习", "其他"]) { const p = parseRelease(`[${c}]Some Title S01 2026 1080p WEB-DL-GRP`); expect(p.category, c).toBe(c); expect(p.titleCn, c).toBeUndefined(); } }); it("数字紧跟普通单词是片名的一部分,不是集号(`Travis - 12 Memories`)", () => { const p = parseRelease("[音乐]Travis - 12 Memories (2003) - WEB-DL - 16bit ALAC-HHWEB"); expect(p.episodes).toBeUndefined(); expect(p.titleEn).toContain("12 Memories"); expect(parseRelease("[Sub] Some Anime - 12 [1080p]").episodes).toEqual({ start: 12, end: 12 }); expect(parseRelease("Some Anime - 05 END [1080p]").episodes).toEqual({ start: 5, end: 5 }); }); it("副标题第一段:主标题没有中文名时取第一个中文名,其余进 altTitles;字幕/简介字段不算片名", () => { const p = parseRelease("Archives S01 2009 1080p WEB-DL H.264 AAC-HHWEB", { subtitle: "档案 / 档案解密 | 第152集 | 1080p | 类型: 纪录片/历史 | 主演: 石凉 [内嵌中字]", }); expect(p).toMatchObject({ titleCn: "档案", titleEn: "Archives", altTitles: ["档案解密"], year: 2009 }); const q = parseRelease("Interstellar 2014 2160p UHD Blu-ray-GRP", { subtitle: "星际穿越 IMAX版/星際啟示錄(港)/英语DTS-HD MA5.1/原盘英简繁&双语特效SUP字幕/章节", }); expect(q.titleCn).toBe("星际穿越"); expect(q.altTitles).toEqual(["星際啟示錄"]); expect(parseRelease("Some Movie 2020 1080p-GRP", { subtitle: "官方中字 | 特效字幕" }).titleCn).toBeUndefined(); }); it("通用词不进比对名称", () => { expect(isGenericAliasName(normalizeTitle("电视剧"))).toBe(true); expect(isGenericAliasName("剧")).toBe(true); expect(isGenericAliasName("tv")).toBe(true); expect(isGenericAliasName(normalizeTitle("档案"))).toBe(false); const p = parseRelease("Lanterns 2026 1080p-GRP"); expect(queryTitles({ ...p, titleCn: "电视剧" })).toEqual(["lanterns"]); }); }); describe("续作 / 章节序号", () => { it("Chapter / Part / 第N章 / 罗马数字符号", () => { expect(sequelNumbers("Mononoke The Movie Chapter III The Curse of the Serpent")).toEqual([3]); expect(sequelNumbers("剧场版物怪:第 3 章:蛇神")).toEqual([3]); expect(sequelNumbers("劇場版モノノ怪 第三章 蛇神")).toEqual([3]); expect(sequelNumbers("Mononoke the Movie: Chapter Ⅱ")).toEqual([2]); expect(sequelNumbers("Dune Part Two")).toEqual([2]); expect(sequelNumbers("Blade Runner 2049")).toEqual([]); }); it("序号冲突直接拒绝,一致加分", () => { const p = parseRelease("Mononoke The Movie Chapter III The Curse of the Serpent 2026 1080p NF WEB-DL-HHWEB"); const c3: IdentityCandidate = { provider: "tmdb", entityType: "movie", externalId: "3", titles: ["剧场版物怪:第 3 章:蛇神"], aliases: ["Mononoke the Movie: Chapter III - The Curse of the Serpent"], year: 2026, }; const c2 = { ...c3, externalId: "2", titles: ["剧场版物怪:第 2 章:火鼠"], aliases: ["Mononoke the Movie: Chapter II"], }; expect(scoreCandidate(p, c3).signals.sequel).toBe("match"); expect(scoreCandidate(p, c2).level).toBe("rejected"); expect(resolveIdentity(p, [c2, c3]).best?.candidate.externalId).toBe("3"); }); }); describe("证据:中英名、通用词、别名佐证、同级取舍", () => { it("中文名精确 + 年份精确 → rule_confirmed;唯一候选 → auto", () => { const p = parseRelease("Martial Master S01 2020 1080p WEB-DL-HHWEB", { subtitle: "武神主宰 | 第685集" }); const r = resolveIdentity(p, [tv("110181", ["武神主宰"], 2020), tv("228707", ["武神主宰"], 2023)]); expect(r).toMatchObject({ status: "auto", best: { level: "rule_confirmed" } }); expect(r.best?.candidate.externalId).toBe("110181"); }); it("通用英文词只靠别名命中且池里撞名 → 封顶 needs_confirm;中文名命中则确认", () => { const noCn = parseRelease("Archives S01 2009 1080p WEB-DL-HHWEB"); const pool = [ tv("201401", ["档案"], 2009, { aliases: ["Archives"] }), tv("1", ["Archives secrètes"], 2021), tv("2", ["Mystères d'archives"], 2009), tv("3", ["The Magnus Archives"], 2016), ]; const r = resolveIdentity(noCn, pool); expect(r.status).toBe("ambiguous"); expect(r.best?.evidence).toContain("generic_name"); const withCn = parseRelease("Archives S01 2009 1080p WEB-DL-HHWEB", { subtitle: "档案 | 第152集" }); expect(resolveIdentity(withCn, pool)).toMatchObject({ status: "auto", best: { candidate: { externalId: "201401" } }, }); }); it("别名命中需要年份或第二种文字的名称佐证", () => { const p = parseRelease("人中之龙第一季 1080p WEB-DL H265 AAC-HHWEB", { subtitle: "人中之龙第一季 | 全98集 | 1080p", }); const r = scoreCandidate(p, tv("256116", ["如龙", "龍が如く"], 2024, { aliases: ["人中之龙"] })); expect(r.level).toBe("needs_confirm"); expect(r.evidence).toContain("alias_uncorroborated"); }); it("同级多候选:年份精确优先,再按命中的名称文字种数", () => { const p = parseRelease("A Prophet S01 2026 2160p TX WEB-DL-HHWEB", { subtitle: "我不是大师 / 长风起 | 第05集" }); const right = tv("289078", ["我不是大师"], 2026, { aliases: ["A Prophet"] }); const other = tv("224006", ["预言者", "Un prophète", "A Prophet"], 2026); const r = resolveIdentity(p, [other, right]); expect(r.status).toBe("auto"); expect(r.best?.candidate.externalId).toBe("289078"); expect(r.reason).toBe("rule_confirmed:tie_break"); // 完全对称 → 仍进池 expect(resolveIdentity(p, [tv("1", ["我不是大师"], 2026), tv("2", ["我不是大师"], 2026)]).status).toBe("ambiguous"); }); it("季号不在候选季列表里扣分", () => { const p = parseRelease("Heart Signal S09 2018 1080p WEB-DL-HHWEB"); const r = scoreCandidate(p, tv("72976", ["Heart Signal"], 2018, { seasonNumbers: [1, 2, 3, 4] })); expect(r.evidence).toContain("season:missing"); expect(r.level).toBe("needs_confirm"); }); it("权重表可覆盖", () => { const p = parseRelease("Oppenheimer 2023 1080p-GRP"); const c: IdentityCandidate = { provider: "tmdb", entityType: "movie", externalId: "1", titles: ["Oppenheimer"], year: 2023, aliases: [], }; expect(scoreCandidate(p, c).level).toBe("rule_confirmed"); expect( scoreCandidate(p, c, { weights: { thresholds: { ...DEFAULT_MATCH_WEIGHTS.thresholds, rule_confirmed: 9 } } }) .level, ).toBe("multi_evidence"); }); });