2 Commits
Author SHA1 Message Date
Daan MeijerandClaude Opus 5.5 3f0b5b8be1 Subject names lose the brackets the counters left unmatched
Test and deploy / test (push) Successful in 3m3s
Test and deploy / deploy (push) Successful in 2m13s
A poster's counter without its "[" ("… Facialized 002/477] - 0177.jpg")
left "… Facialized ]" as the release name (11286967). displayName (TS) and
display_name (Rust) drop unmatched brackets from subject and post names
only; the identity key keeps the remainder as it was, so no release splits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:04:06 +01:00
Daan MeijerandClaude Opus 5.5 c6f9409e7f REBUILD.md: the namer's failures were the partial releases, 52k of 210k, not nearly all
Test and deploy / test (push) Successful in 3m9s
Test and deploy / deploy (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:07:24 +01:00
5 changed files with 92 additions and 8 deletions

No files matched your search

+2 -2
View File
@@ -882,8 +882,8 @@ f. **Nothing enters the catalog without a usable name** (amended
chunks) or by XOVER on a connection of its own, through the TypeScript
indexer with `partialFiles` (a file given up short of parts stays a
partial file of its release, as the Rust window writes it; without it
the range path's fragment rule left nothing to find, and 10 of 148k named
releases were moved by 2026-10-29), written as a release under the pending key with `source`
the range path's fragment rule left nothing to find: by 2026-10-29
about 52k of 210k named releases failed, all partial), written as a release under the pending key with `source`
weak-stem, the name as `inner_name`, its chunk and its candidates (the
prober answers those from `probe_cache`: inspections, par2 sets, hashes,
the password flag). Not built: the spot/NZB match on pending keys (step
+51 -2
View File
@@ -449,6 +449,43 @@ fn finish_remainder(s: &str) -> String {
js::trim(&out).to_string()
}
/// displayName: a subject name as shown, brackets the counters left unmatched removed ("… Facialized 002/477] - …"
/// gave "… Facialized ]"). Only the name: the key keeps the remainder.
pub fn display_name(remainder: &str) -> String {
let b = remainder.as_bytes();
if !b.iter().any(|c| matches!(c, b'[' | b']' | b'(' | b')')) {
return remainder.to_string();
}
let mut drop = vec![false; b.len()];
for (open, close) in [(b'[', b']'), (b'(', b')')] {
let mut opens: Vec<usize> = Vec::new();
for (i, &c) in b.iter().enumerate() {
if c == open {
opens.push(i);
} else if c == close && opens.pop().is_none() {
drop[i] = true;
}
}
for i in opens {
drop[i] = true;
}
}
if !drop.contains(&true) {
return remainder.to_string();
}
let kept: Vec<u8> = b.iter().zip(&drop).filter(|(_, &d)| !d).map(|(&c, _)| c).collect();
let kept = String::from_utf8(kept).expect("only ASCII brackets removed");
let mut out = String::with_capacity(kept.len());
for c in kept.chars() {
if c == ' ' && out.ends_with(' ') {
continue;
}
out.push(c);
}
let name = js::trim(&out);
if name.is_empty() { remainder.to_string() } else { name.to_string() }
}
/// counterTotal: the subject's file counter total, first form found first.
pub fn counter_total(subject: &str) -> Option<String> {
let s = replace(&QUOTED, subject, " ", true);
@@ -841,11 +878,11 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
if !image && test(&RANDOM_VOLUME, name) && stem.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-') && looks_random(&stripped) {
parts.push(&stem);
}
return Release { key: hash(&parts), name: remainder, source: "subject", file_total };
return Release { key: hash(&parts), name: display_name(&remainder), source: "subject", file_total };
}
if let Some(total) = post_total.filter(|&n| n != 0 && !tag) {
if is_post_token(&remainder) {
return Release { key: hash(&["post", poster, &remainder, &total.to_string()]), name: remainder, source: "post", file_total };
return Release { key: hash(&["post", poster, &remainder, &total.to_string()]), name: display_name(&remainder), source: "post", file_total };
}
}
// A scene music post's index files ("00-ammar-turk_uit_de_kroeg-single-web-nl-2022-cover.jpg") under a bare counter go
@@ -1339,6 +1376,18 @@ mod tests {
assert_eq!(file_stem_re("Morning Lovers.wmv.058"), "Morning Lovers");
}
#[test]
fn display_names() {
let porn4you = "webmaster@hdbangvidz.com (Porn4You)";
let a = release_identity(porn4you, "Babe with Plump Bubble Butt Fucked and Facialized 002/477] - 0178.jpg", "0178.jpg");
let b = release_identity(porn4you, "Babe with Plump Bubble Butt Fucked and Facialized 003/477] - 0179.jpg", "0179.jpg");
assert_eq!(a.name, "Babe with Plump Bubble Butt Fucked and Facialized");
assert_eq!(a.key, b.key);
assert_eq!(display_name("[Group] Show (2020)"), "[Group] Show (2020)");
assert_eq!(display_name("( Show [x] name"), "Show [x] name");
assert_eq!(display_name("]"), "]");
}
#[test]
fn remainders() {
assert_eq!(subject_remainder(r#"Some.Release [01/45] - "x.rar" yEnc (1/50) 7168"#), "Some Release");
+25 -2
View File
@@ -105,6 +105,29 @@ export function subjectRemainder(subject: string): string {
.trim()
}
/**
* A subject name as shown: brackets the counters left unmatched removed ("… Facialized 002/477] - 0177.jpg", the
* counter's "[" missing, gave "… Facialized ]"; LOG.md 2026-10-29 (121)). Only the name: the key keeps the remainder.
*/
export function displayName(remainder: string): string {
if (!/[[\]()]/.test(remainder)) return remainder
const drop = new Set<number>()
for (const [open, close] of [['[', ']'], ['(', ')']]) {
const opens: number[] = []
for (let i = 0; i < remainder.length; i++) {
if (remainder[i] === open) opens.push(i)
else if (remainder[i] === close) {
if (opens.length > 0) opens.pop()
else drop.add(i)
}
}
for (const i of opens) drop.add(i)
}
if (drop.size === 0) return remainder
const name = remainder.split('').filter((_, i) => !drop.has(i)).join('').replace(/ {2,}/g, ' ').trim()
return name || remainder
}
/**
* Posting tools put random per-file tokens and poster IDs in subjects (e.g.
* `elys-de81bcabef5faf6a - [Nyh5Q07Y]`); only text that still reads like a
@@ -448,7 +471,7 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
if (!image && RANDOM_VOLUME.test(input.name) && /^[A-Za-z0-9_-]+$/.test(stem) && looksRandom(stem.replace(/[-_]/g, ''))) parts.push(stem)
return {
key: hash(...parts),
name: remainder,
name: displayName(remainder),
fileTotal,
source: 'subject',
}
@@ -456,7 +479,7 @@ export function releaseIdentity(input: ReleaseInput): ReleaseIdentity | null {
// Not a name, but a post id (e.g. `83110629-n [10/18] - "random"`): with the
// [i/M] counter it groups the files of one post whose names are random.
if (!tag && postTotal && isPostToken(remainder)) {
return { key: hash('post', poster, remainder, postTotal), name: remainder, fileTotal, source: 'post' }
return { key: hash('post', poster, remainder, postTotal), name: displayName(remainder), fileTotal, source: 'post' }
}
// A scene music post's index files under a bare counter go with its tracks: the artist and the total ("[01/13] -
// "00-ammar-turk_uit_de_kroeg-single-web-nl-2022-cover.jpg"" with "[05/13] - "01-ammar-turk_uit_de_kroeg.mp3"": the
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Grouping of files into releases (downloads), independent of AdonisJS.
*/
export { releaseIdentity, par2ReleaseIdentity, par2ReleaseName, rarReleaseIdentity, isGenericName, fileStem, sizeAfterName, runOn, subjectRemainder, namesRelease, isPostToken, type ReleaseIdentity, type ReleaseInput } from './identity.js'
export { releaseIdentity, displayName, par2ReleaseIdentity, par2ReleaseName, rarReleaseIdentity, isGenericName, fileStem, sizeAfterName, runOn, subjectRemainder, namesRelease, isPostToken, type ReleaseIdentity, type ReleaseInput } from './identity.js'
export { looksRandom } from './random.js'
+13 -1
View File
@@ -1,5 +1,5 @@
import { test } from '@japa/runner'
import { fileStem, releaseIdentity, runOn, sizeAfterName, subjectRemainder } from '#lib/release/index'
import { displayName, fileStem, releaseIdentity, runOn, sizeAfterName, subjectRemainder } from '#lib/release/index'
import { looksLikeFilename, weakFileKey } from '#lib/headers/index'
test.group('release / fileStem', () => {
@@ -604,3 +604,15 @@ test.group('release / par2 names', () => {
assert.equal(id('[2/6] - "x.jpg" yEnc', 'x.jpg').key, id('[1/6] - "x.part01.rar" yEnc', 'x.part01.rar').key)
})
})
test.group('release / displayName', () => {
test('brackets the counters left unmatched leave the name, the key keeps them', ({ assert }) => {
const subject = (i: number) => `Babe with Plump Bubble Butt Fucked and Facialized ${String(i).padStart(3, '0')}/477] - 0${176 + i}.jpg`
const id = (i: number) => releaseIdentity({ poster: 'webmaster@hdbangvidz.com (Porn4You)', weak: false, subject: subject(i), name: `0${176 + i}.jpg` })!
assert.equal(id(2).name, 'Babe with Plump Bubble Butt Fucked and Facialized')
assert.equal(id(2).key, id(3).key)
assert.equal(displayName('[Group] Show (2020)'), '[Group] Show (2020)')
assert.equal(displayName('( Show [x] name'), 'Show [x] name')
assert.equal(displayName(']'), ']')
})
})