More per-file suffixes: old rar volumes, .rev, a part's par2, .bc; a size after an unquoted name
Each file of these uploads was a release of its own: - "BD_NoTHing018.s41", "…CyjI.s00": rar volumes after .r99 (as the file's own lower-case extension only, so "show.s01.rar" keeps its season); - "chappelle1.part02.rev": rar recovery volumes; - "X.part1.par2", "X.part1.vol00+34.par2" beside "X.part1.rar"; - "NZPEC….180.bc": BCArchive volumes; - "[27/70] - 2407.jpg - 28": the size in kB after an unquoted name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2823d42bfa
commit
d4e53ca0d8
4 files changed
+127
-17
No files matched your search
+80
-7
@@ -28,11 +28,14 @@ fn test(re: &Regex, s: &str) -> bool {
|
||||
|
||||
static SUFFIXES: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
|
||||
vec![
|
||||
// A part's par2 files ("X.part1.par2", "X.part1.vol00+34.par2").
|
||||
(re(r"(?i)\.part[0-9]+(?:\.vol[0-9]+[+-][0-9]+)?\.par2$"), ""),
|
||||
(re(r"(?i)\.vol[0-9]+[+-][0-9]+\.par2$"), ""),
|
||||
(re(r"(?i)\.par2$"), ""),
|
||||
(re(r"(?i)\.part[0-9]+\.rar$"), ""),
|
||||
(re(r"(?i)\.part[0-9]+\.(?:rar|rev)$"), ""),
|
||||
(re(r"(?i)\.r[0-9]{2,3}$"), ""),
|
||||
(re(r"(?i)\.rar$"), ""),
|
||||
(re(r"(?i)\.(?:rar|rev)$"), ""),
|
||||
(re(r"(?i)(?:\.[0-9]{1,4})?\.bc$"), ""),
|
||||
(re(r"(?i)\.7z(\.[0-9]{3})?$"), ""),
|
||||
(re(r"(?i)\.zip(\.[0-9]{3})?$"), ""),
|
||||
// JS: (?<=\.(7z|…))\.\d{3}$ — no lookbehind here, so the extension is matched and put back.
|
||||
@@ -47,7 +50,7 @@ static SUFFIXES: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
|
||||
/// fileStem with the regexes, as in the TypeScript (the reference for file_stem's tests).
|
||||
#[allow(dead_code)]
|
||||
pub fn file_stem_re(name: &str) -> String {
|
||||
let mut stem = js::trim(name).to_string();
|
||||
let mut stem = replace(&OLD_RAR_VOLUME, js::trim(name), "", false);
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
@@ -65,6 +68,8 @@ pub fn file_stem_re(name: &str) -> String {
|
||||
stem
|
||||
}
|
||||
|
||||
/// Old-style rar volumes after .r99, as the file's own extension, lower case: /\.[s-z]\d{2}$/.
|
||||
static OLD_RAR_VOLUME: LazyLock<Regex> = LazyLock::new(|| re(r"\.[s-z][0-9]{2}$"));
|
||||
static QUOTED: LazyLock<Regex> = LazyLock::new(|| re(r#""[^"]*""#));
|
||||
static COUNTERS: LazyLock<Regex> = LazyLock::new(|| re(r"\[[0-9]+/[0-9]+\]|\([0-9]+/[0-9]+\)"));
|
||||
static YENC_END: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\byEnc\b(SP+[0-9]+)?SP*$"));
|
||||
@@ -145,9 +150,9 @@ fn strip_suffix_rule(s: &[u8], rule: usize) -> Option<usize> {
|
||||
eq_ci(&s[v..v + 4], b".vol").then_some(v)
|
||||
}
|
||||
1 => ends_ci(s, b".par2").then(|| n - 5),
|
||||
// \.part\d+\.rar$
|
||||
// \.part\d+\.(?:rar|rev)$
|
||||
2 => {
|
||||
if !ends_ci(s, b".rar") {
|
||||
if !ends_ci(s, b".rar") && !ends_ci(s, b".rev") {
|
||||
return None;
|
||||
}
|
||||
let d = digits_back(s, n - 4);
|
||||
@@ -160,7 +165,7 @@ fn strip_suffix_rule(s: &[u8], rule: usize) -> Option<usize> {
|
||||
let p = n.checked_sub(d + 2).filter(|_| (2..=3).contains(&d))?;
|
||||
eq_ci(&s[p..p + 2], b".r").then_some(p)
|
||||
}
|
||||
4 => ends_ci(s, b".rar").then(|| n - 4),
|
||||
4 => (ends_ci(s, b".rar") || ends_ci(s, b".rev")).then(|| n - 4),
|
||||
// \.7z(\.\d{3})?$ and \.zip(\.\d{3})?$
|
||||
5 | 6 => {
|
||||
let ext: &[u8] = if rule == 5 { b".7z" } else { b".zip" };
|
||||
@@ -181,6 +186,25 @@ fn strip_suffix_rule(s: &[u8], rule: usize) -> Option<usize> {
|
||||
9 => strip_ext(s, &[b"tar"]),
|
||||
10 => strip_ext(s, &PACK_EXTS),
|
||||
11 => strip_ext(s, &MEDIA_EXTS),
|
||||
// \.part\d+(?:\.vol\d+[+-]\d+)?\.par2$
|
||||
12 => {
|
||||
if !ends_ci(s, b".par2") {
|
||||
return None;
|
||||
}
|
||||
let m = strip_suffix_rule(s, 0).unwrap_or(n - 5);
|
||||
let d = digits_back(s, m);
|
||||
let p = m.checked_sub(d + 5).filter(|_| d > 0)?;
|
||||
eq_ci(&s[p..p + 5], b".part").then_some(p)
|
||||
}
|
||||
// (?:\.\d{1,4})?\.bc$
|
||||
13 => {
|
||||
if !ends_ci(s, b".bc") {
|
||||
return None;
|
||||
}
|
||||
let m = n - 3;
|
||||
let d = digits_back(s, m);
|
||||
Some(if (1..=4).contains(&d) && m > d && s[m - d - 1] == b'.' { m - d - 1 } else { m })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -191,10 +215,15 @@ pub fn file_stem(name: &str) -> String {
|
||||
let stem = js::trim(name);
|
||||
let b = stem.as_bytes();
|
||||
let mut len = b.len();
|
||||
// Old-style rar volumes after .r99 (".s41"), as the file's own extension, lower case.
|
||||
if len >= 4 && b[len - 4] == b'.' && (b's'..=b'z').contains(&b[len - 3]) && b[len - 2..].iter().all(u8::is_ascii_digit) {
|
||||
len -= 4;
|
||||
}
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
for rule in 0..12 {
|
||||
// In the order of SUFFIXES: a part's par2 first, BCArchive volumes after the rar rules.
|
||||
for rule in [12, 0, 1, 2, 3, 4, 13, 5, 6, 7, 8, 9, 10, 11] {
|
||||
if let Some(next) = strip_suffix_rule(&b[..len], rule) {
|
||||
if next > 0 && next < len {
|
||||
len = next;
|
||||
@@ -638,6 +667,26 @@ pub fn random_poster(from: &str) -> bool {
|
||||
looks_random(&local)
|
||||
}
|
||||
|
||||
/// sizeAfterName (lib/release/identity.ts): the subject without a number after the file's unquoted name at its end.
|
||||
pub fn size_after_name(subject: &str, name: &str) -> String {
|
||||
let end = subject.trim_end_matches(js::is_space);
|
||||
let before_digits = end.trim_end_matches(|c: char| c.is_ascii_digit());
|
||||
if before_digits.len() == end.len() {
|
||||
return subject.to_string();
|
||||
}
|
||||
// /\s*-\s*$/
|
||||
let t = before_digits.trim_end_matches(js::is_space);
|
||||
let before = match t.strip_suffix('-') {
|
||||
Some(t) => t.trim_end_matches(js::is_space),
|
||||
None => return subject.to_string(),
|
||||
};
|
||||
if !name.is_empty() && before.ends_with(name) && !before.ends_with(&format!("\"{name}\"")) {
|
||||
before.to_string()
|
||||
} else {
|
||||
subject.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
let poster = if random_poster(poster) { "" } else { poster };
|
||||
// The post rule's key takes the [i/M] total; the release's file total any counter's (counterTotal).
|
||||
@@ -646,6 +695,9 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
|
||||
// A name that isn't quoted (unquoted_name) is taken out as the quotes would have been.
|
||||
// The subject here has its spaces collapsed; the name may not.
|
||||
// Other quoted text stays: it names the release (">>> CREATIVE COMMONS NZB <<< "0190 alarm 5.0 " - File 1 of 9: …").
|
||||
// A size after a name that isn't quoted, at the end ("[27/70] - 2407.jpg - 28").
|
||||
let subject = size_after_name(subject, name);
|
||||
let subject = subject.as_str();
|
||||
let quoted = format!("\"{name}\"");
|
||||
let without = if subject.contains("ed) {
|
||||
subject.replacen("ed, " ", 1)
|
||||
@@ -1370,6 +1422,27 @@ mod image_set_tests {
|
||||
#[test]
|
||||
fn split_rar_volumes_and_sizes_after_the_counter() {
|
||||
assert_eq!(super::file_stem("rld-gtre.r13.002"), "rld-gtre");
|
||||
for (n, stem) in [
|
||||
("BD_NoTHing018.s41", "BD_NoTHing018"),
|
||||
("x8ZRmX5N83mrokOwqJtoIGHTrqXQrgCyj.s00", "x8ZRmX5N83mrokOwqJtoIGHTrqXQrgCyj"),
|
||||
("the.office.us.s01.rar", "the.office.us.s01"),
|
||||
("Some.Show.S01", "Some.Show.S01"),
|
||||
("chappelle1.part02.rev", "chappelle1"),
|
||||
("chappelle1.rev", "chappelle1"),
|
||||
("VwE3i90MVX9dqMFuDfzdS7Q6.part1.par2", "VwE3i90MVX9dqMFuDfzdS7Q6"),
|
||||
("VwE3i90MVX9dqMFuDfzdS7Q6.part1.vol00+34.par2", "VwE3i90MVX9dqMFuDfzdS7Q6"),
|
||||
("Movie.Part1.mkv", "Movie.Part1"),
|
||||
("NZPECQUztWOponTpCratvvTPDBXBjvHIULOUPvSH.180.bc", "NZPECQUztWOponTpCratvvTPDBXBjvHIULOUPvSH"),
|
||||
("x.12345.bc", "x.12345"),
|
||||
] {
|
||||
assert_eq!(super::file_stem(n), stem, "{n}");
|
||||
assert_eq!(super::file_stem_re(n), stem, "{n}");
|
||||
}
|
||||
assert_eq!(super::size_after_name("(Hot MILF getting banged hard)[27/70] - 2407.jpg - 28", "2407.jpg"), "(Hot MILF getting banged hard)[27/70] - 2407.jpg");
|
||||
assert_eq!(super::size_after_name(r#"Show [01/10] - "a.jpg" - 28"#, "a.jpg"), r#"Show [01/10] - "a.jpg" - 28"#);
|
||||
assert_eq!(super::size_after_name("Show - Episode 05", "x.mkv"), "Show - Episode 05");
|
||||
let milf = |i: &str, n: &str, kb: &str| super::release_identity("p", &format!("(Hot MILF getting banged hard)[{i}/70] - {n} - {kb}"), n);
|
||||
assert_eq!(milf("27", "2407.jpg", "28").key, milf("28", "2408.jpg", "31").key);
|
||||
assert_eq!(super::file_stem_re("rld-gtre.r13.002"), "rld-gtre");
|
||||
let k = |i: u32, size: u32, n: &str| release_identity("Kingdn", &format!(r#"Buffy The Vampire Slayer s02e04 [{i}/32] - [{size}] "{n}" yEnc"#), n);
|
||||
assert_eq!(k(26, 4929, "Buffy s02e04 xvid-tns.part24.rar").key, k(27, 400, "Buffy s02e04 xvid-tns.vol00+01.PAR2").key);
|
||||
|
||||
Reference in new issue
Block a user