feat(search): align quick-filter facets with MP (SEARCH-4)

Add audioCodec, subtitle, dub, feature, year, category, size and seeders
facets with disjunctive counts; parser records dub languages (audioLangs).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 22:40:55 +08:00
1 parent c3b25be676
commit d9cf8f1c3a
10 files changed
+409 -5

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
export { cnToInt } from "./cn-number.ts";
export { evalEpisodeOffset, normalizeOffsetExpression, OffsetExpressionError } from "./offset.ts";
export { type CnMarkers, confidenceOf, fileKindOf, parseRelease, scanCnMarkers } from "./parse.ts";
export { type CnMarkers, confidenceOf, fileKindOf, parseRelease, scanAudioLangs, scanCnMarkers } from "./parse.ts";
export { mergeParsed, type PathSegments, parseReleasePath, splitReleasePath } from "./path.ts";
export { buildGroupRegex, RELEASE_GROUP_SEED } from "./release-groups.ts";
export * from "./types.ts";
@@ -188,6 +188,7 @@ export interface CnMarkers {
contentType?: ContentType;
variantTags: string[];
subtitles?: string[];
audioLangs?: string[];
evidence: string[];
}
@@ -204,6 +205,18 @@ const SUB_KEYWORDS =
/字幕|中字|双字|雙字|内封|內封|内嵌|內嵌|外挂|外掛|简繁|簡繁|简中|繁中|简体|简日|繁日|繁体|繁體|簡體|简英|繁英|中英|双语|雙語/u;
const AUDIO_LANG_RE = /国粤双语|國粵雙語|国英双语|國英雙語|国语配音|國語配音|国配|國配|国语|國語|粤语|粵語/gu;
/** 配音语言标记(国语 / 粤语 / 国英双语…)→ cmn / yue / en */
export function scanAudioLangs(text: string): string[] {
const out: string[] = [];
for (const m of text.matchAll(AUDIO_LANG_RE)) {
const t = m[0];
if (/[国國]/u.test(t) && !out.includes("cmn")) out.push("cmn");
if (/[粤粵]/u.test(t) && !out.includes("yue")) out.push("yue");
if (/英/u.test(t) && !out.includes("en")) out.push("en");
}
return out;
}
function scanSubtitleLangs(run: string): string[] {
const langs: string[] = [];
const add = (l: string) => {
@@ -261,6 +274,11 @@ export function scanCnMarkers(input: string): { s: string } & CnMarkers {
out.totalEpisodes = cnToInt((m[1] ?? m[2])!);
out.evidence.push("total:cn");
});
const dubs = scanAudioLangs(s);
if (dubs.length) {
out.audioLangs = dubs;
out.evidence.push("audio_lang:cn");
}
s = s.replace(AUDIO_LANG_RE, " ");
for (const [re, ct, ev] of CONTENT_RULES) {
take(re, () => {
@@ -1097,6 +1115,10 @@ function applyCnMarkers(d: Draft, cm: CnMarkers, prefix: string): void {
d.subtitles = [...new Set([...(d.subtitles ?? []), ...cm.subtitles])];
note("subtitle");
}
if (cm.audioLangs) {
d.audioLangs = [...new Set([...(d.audioLangs ?? []), ...cm.audioLangs])];
note("audio_lang");
}
}
function finalize(d: Draft, opts: ParseOptions): ParsedRelease {
@@ -109,6 +109,8 @@ export interface ParsedRelease {
part?: string;
/** 字幕语言(zh / zh-Hans / zh-Hant / en / ja / ko);undefined = 标题无信息(未知),不是"无字幕" */
subtitles?: string[];
/** 配音语言(cmn 国语 / yue 粤语 / en 英语,来自「国语」「国粤双语」等标记);undefined = 标题无信息 */
audioLangs?: string[];
sizeBytes?: number;
contentType: ContentType;
segment?: Segment;