- release-parser: title/file/dir parsing with MP parity + deviations (airDate/issue, cut, release group suffix fallback, dir+file merge, safe episode offset) - identity: title normalization (NFKC, trad->simp map, season suffix) and evidence-level candidate ranking (year tolerance ±1, no probabilities) - slot: hard/soft/upgrade evaluation (missing vs unmet fields), state machine, movie/unit completion (season pack must be covered by actual file list) - acquisition: new_title/catch_up/upgrade modes, priority order, budget, cache volume hard threshold with resume watermark (no double counting) - path: immutable path bindings, restricted naming templates, sanitize, conflict detection, downloader<->host path mapping - organize: planOrganize with sidecar pairing, sample exclusion, existing target policy, crossDevice marking, H&R -> conflict for move - download-state: qB state mapping, completion, no-progress evidence 230 tests, ~97% statement coverage. Co-Authored-By: Claude Fable 5.1 <[email protected]>
39 lines
893 B
TypeScript
39 lines
893 B
TypeScript
const DIGITS: Record<string, number> = {
|
||
零: 0,
|
||
〇: 0,
|
||
一: 1,
|
||
二: 2,
|
||
两: 2,
|
||
兩: 2,
|
||
三: 3,
|
||
四: 4,
|
||
五: 5,
|
||
六: 6,
|
||
七: 7,
|
||
八: 8,
|
||
九: 9,
|
||
};
|
||
const UNITS: Record<string, number> = { 十: 10, 拾: 10, 百: 100, 千: 1000 };
|
||
|
||
/** 中文数字/阿拉伯数字 → 整数;无法解析返回 undefined */
|
||
export function cnToInt(s: string): number | undefined {
|
||
const t = s.trim();
|
||
if (/^\d+$/.test(t)) return Number.parseInt(t, 10);
|
||
if (t === "" || ![...t].every((c) => c in DIGITS || c in UNITS)) return undefined;
|
||
let total = 0;
|
||
let current = 0;
|
||
for (const c of t) {
|
||
const d = DIGITS[c];
|
||
if (d !== undefined) {
|
||
current = d;
|
||
continue;
|
||
}
|
||
const u = UNITS[c]!;
|
||
total += (current || 1) * u;
|
||
current = 0;
|
||
}
|
||
return total + current;
|
||
}
|
||
|
||
export const CN_NUM = "[0-9零〇一二两兩三四五六七八九十拾百千]+";
|