Configurable DEFAULT_MATCH_WEIGHTS; Chinese/English names both count and corroborate; Chapter/Part/第N章 sequel conflicts reject; missing season penalised; category words and single-word aliases colliding in the pool cannot confirm alone; alias-only hits need year or second-script support; same-level ties broken by exact year then matched name scripts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
104 lines
2.5 KiB
TypeScript
104 lines
2.5 KiB
TypeScript
import { CN_NUM } from "../release-parser/cn-number.ts";
|
||
import { toSimplified } from "./zh-map.ts";
|
||
|
||
const SEASON_SUFFIX = [
|
||
new RegExp(`\\s*第\\s*${CN_NUM}\\s*[季部]\\s*$`, "u"),
|
||
/\s*[::-]?\s*(?:season|series)\s*\d+\s*$/iu,
|
||
/\s+S\d{1,2}\s*$/iu,
|
||
/\s*[((]\s*(?:19|20)\d{2}\s*[))]\s*$/u,
|
||
];
|
||
|
||
/** 去掉标题末尾的季号/年份后缀("庆余年 第二季"、"Show Season 2"、"Title (2019)") */
|
||
export function stripSeasonSuffix(s: string): string {
|
||
let out = s.trim();
|
||
for (let changed = true; changed; ) {
|
||
changed = false;
|
||
for (const re of SEASON_SUFFIX) {
|
||
const next = out.replace(re, "");
|
||
if (next !== out && next.trim().length > 0) {
|
||
out = next.trim();
|
||
changed = true;
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 标题归一化(用于比较,不用于展示):
|
||
* 全角→半角(NFKC)、繁→简、小写、& → and、去季号后缀、去标点与空白。
|
||
*/
|
||
export function normalizeTitle(s: string): string {
|
||
const base = stripSeasonSuffix(toSimplified(s.normalize("NFKC")));
|
||
return base
|
||
.toLowerCase()
|
||
.replace(/&/g, "and")
|
||
.replace(/[^\p{L}\p{N}]+/gu, "");
|
||
}
|
||
|
||
/** 分类词(站点分类 / RSS 标题前缀)与常见停用词:不能当片名或别名用于身份比对(ID-2) */
|
||
const GENERIC_NAMES = new Set(
|
||
[
|
||
"电影",
|
||
"电视剧",
|
||
"剧集",
|
||
"连续剧",
|
||
"动漫",
|
||
"动画",
|
||
"番剧",
|
||
"国漫",
|
||
"日漫",
|
||
"综艺",
|
||
"纪录片",
|
||
"记录片",
|
||
"音乐",
|
||
"短剧",
|
||
"微短剧",
|
||
"体育",
|
||
"游戏",
|
||
"软件",
|
||
"学习",
|
||
"其他",
|
||
"其它",
|
||
"未知",
|
||
"新",
|
||
"合集",
|
||
"全集",
|
||
"中字",
|
||
"国语",
|
||
"粤语",
|
||
"高清",
|
||
"蓝光",
|
||
"原盘",
|
||
"movie",
|
||
"movies",
|
||
"tv",
|
||
"tvseries",
|
||
"series",
|
||
"anime",
|
||
"animation",
|
||
"documentary",
|
||
"music",
|
||
"variety",
|
||
"show",
|
||
"the",
|
||
"season",
|
||
"complete",
|
||
"unknown",
|
||
"other",
|
||
].map((s) => s.toLowerCase()),
|
||
);
|
||
|
||
/**
|
||
* 归一化后的名称是否太"通用"不能用于身份比对:分类词、停用词、单个汉字、两个以内的拉丁字符、纯数字。
|
||
* 参数是 normalizeTitle 之后的形态。
|
||
*/
|
||
export function isGenericAliasName(norm: string): boolean {
|
||
if (!norm) return true;
|
||
if (GENERIC_NAMES.has(norm)) return true;
|
||
if (/^\d+$/.test(norm)) return true;
|
||
const han = norm.match(/\p{Script=Han}/gu)?.length ?? 0;
|
||
if (han > 0) return [...norm].length < 2;
|
||
return norm.length <= 2;
|
||
}
|