diff --git a/edge/src/names.rs b/edge/src/names.rs index 1c80f6e..ef2089c 100644 --- a/edge/src/names.rs +++ b/edge/src/names.rs @@ -383,8 +383,10 @@ pub fn counter_total(subject: &str) -> Option { let total = c.get(2).or(c.get(4)).unwrap().as_bytes(); return Some(js_number_string(number(total))); } + // Dates are no counters ("08-29-2011" read as 8 of 29). + let undated = replace(&DATES, &s, " ", true); for re in [&*OF_COUNTER_OPEN, &*DASH_COUNTER, &*SLASH_COUNTER, &*ZERO_COUNTER] { - for c in re.captures_iter(s.as_bytes()) { + for c in re.captures_iter(undated.as_bytes()) { if number(&c[1]) <= number(&c[2]) { return Some(js_number_string(number(&c[2]))); } @@ -393,6 +395,7 @@ pub fn counter_total(subject: &str) -> Option { None } +static DATES: LazyLock = LazyLock::new(|| re(r"\b(?:(?:19|20)[0-9]{2}[-._ ](?:0?[1-9]|1[0-2])[-._ ][0-9]{1,2}|[0-9]{1,2}[-._ ][0-9]{1,2}[-._ ](?:19|20)[0-9]{2}|[0-9]{1,2}[-._](?:19|20)[0-9]{2})\b")); static COUNTERS_CAPTURE: LazyLock = LazyLock::new(|| re(r"\[([0-9]+)/([0-9]+)\]|\(([0-9]+)/([0-9]+)\)")); /// The "of" counter without its closing bracket (as counterTotal's pattern). static OF_COUNTER_OPEN: LazyLock = LazyLock::new(|| re(r"(?i)[\[(]?\b(?:fileSP+)?([0-9]+)SP*ofSP*([0-9]+)\b")); @@ -1114,6 +1117,16 @@ mod image_set_tests { assert!(!random_poster("w.merklein@freenet.de (muffin)")); } + #[test] + fn dates_are_no_counters() { + use super::counter_total; + assert_eq!(counter_total("Avira AntiVir Rescue System 08-29-2011.rar"), None); + assert_eq!(counter_total("Android Apps and Games Pack 27-02-2011.rar"), None); + assert_eq!(counter_total("Supernova-clubbin (slamfm)-cable-18-08-2012-1king.rar"), None); + assert_eq!(counter_total(r#"Some.Album [03/12] - "x.mp3""#).as_deref(), Some("12")); + assert_eq!(counter_total(r#"File 07390 of 19411 "x.epub""#).as_deref(), Some("19411")); + } + #[test] fn more_splits_from_the_reindex() { use super::random_poster; diff --git a/lib/release/identity.ts b/lib/release/identity.ts index 93f22d3..43346d7 100644 --- a/lib/release/identity.ts +++ b/lib/release/identity.ts @@ -138,12 +138,18 @@ const DOCUMENT = /\.(pdf|epub|mobi|azw3?|djvu|cbr|cbz|cb7|chm|lit|docx?)$/i * removes, the first form found first: [i/M] or (i/M), "i of M", [i-M], i/M, * 0i-M (the last four only when i <= M). */ +/** Dates in names: 2011-08-29, 08-29-2011 or 29.08.2011, 02-2011. */ +const DATES = /\b(?:(?:19|20)\d{2}[-._ ](?:0?[1-9]|1[0-2])[-._ ]\d{1,2}|\d{1,2}[-._ ]\d{1,2}[-._ ](?:19|20)\d{2}|\d{1,2}[-._](?:19|20)\d{2})\b/g + export function counterTotal(subject: string): number | undefined { const s = subject.replace(/"[^"]*"/g, ' ') const plain = /\[(\d+)\/(\d+)\]|\((\d+)\/(\d+)\)/.exec(s) if (plain) return Number(plain[2] ?? plain[4]) + // Dates are no counters ("Avira AntiVir Rescue System 08-29-2011.rar", "… 27-02-2011", "18-08-2012"): + // read as "8 of 29", such a release showed a wrong file total and counted as incomplete. + const undated = s.replace(DATES, ' ') for (const pattern of [/[[(]?\b(?:file\s+)?(\d+)\s*of\s*(\d+)\b/gi, /\[(\d+)-(\d+)\]/g, /\b(\d+)\/(\d+)\b/g, /\b(0\d*)-(\d+)\b/g]) { - for (const m of s.matchAll(pattern)) if (Number(m[1]) <= Number(m[2])) return Number(m[2]) + for (const m of undated.matchAll(pattern)) if (Number(m[1]) <= Number(m[2])) return Number(m[2]) } return undefined } diff --git a/tests/unit/release/identity.spec.ts b/tests/unit/release/identity.spec.ts index 7cfba76..444d456 100644 --- a/tests/unit/release/identity.spec.ts +++ b/tests/unit/release/identity.spec.ts @@ -214,6 +214,15 @@ test.group('release / par2 names', () => { assert.isFalse(randomPoster('w.merklein@freenet.de (muffin)')) }) + test('dates are no file counters', async ({ assert }) => { + const { counterTotal } = await import('#lib/release/identity') + assert.isUndefined(counterTotal('Avira AntiVir Rescue System 08-29-2011.rar')) + assert.isUndefined(counterTotal('Android Apps and Games Pack 27-02-2011.rar')) + assert.isUndefined(counterTotal('Supernova-clubbin (slamfm)-cable-18-08-2012-1king.rar')) + assert.equal(counterTotal('Some.Album [03/12] - "x.mp3"'), 12) + assert.equal(counterTotal('File 07390 of 19411 "x.epub"'), 19411) + }) + test('split parts with two-digit numbers share a stem', ({ assert }) => { assert.equal(fileStem('Admiral.EP05.2008.720p.Rus.HDCLUB.mkv.21'), fileStem('Admiral.EP05.2008.720p.Rus.HDCLUB.mkv.22')) assert.equal(fileStem('Naslednica_po_prjamoy_DVDRip.avi.05'), 'Naslednica_po_prjamoy_DVDRip')