Hidden archive entries no longer name releases
Test and deploy / test (push) Successful in 1m44s
Test and deploy / deploy (push) Successful in 2m6s

One poster adds the same 22 GB hidden '.1917-….mkv' decoy to every 7z
upload; the largest-entry rule named 840 releases after it. Entries with
a hidden path part are skipped; release:rename --hidden renames the
existing ones from their stored listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-06 10:29:05 +02:00
1 parent 4d3cb6474c
commit 51975fe603
3 files changed
+57 -5

No files matched your search

+35 -3
View File
@@ -115,9 +115,11 @@ export async function extractEntry(archive: Buffer, entry: RarEntry, maxBytes =
* `Joker.Folie.a.Deux.2024.2160p….nzb`). Samples are skipped.
*/
export function releaseNameFromContents(info: Pick<RarInfo, 'entries'>): string | undefined {
const entries = info.entries.filter((e) => !e.directory)
const tops = new Set(info.entries.map((e) => e.name.split(/[\\/]/)[0]))
if (tops.size === 1 && info.entries.some((e) => /[\\/]/.test(e.name))) {
// Hidden entries name nothing: one poster adds the same 22 GB ".1917-….mkv" decoy to every upload (LOG.md 2026-10-06 (2)).
const listed = info.entries.filter((e) => !isHidden(e.name))
const entries = listed.filter((e) => !e.directory)
const tops = new Set(listed.map((e) => e.name.split(/[\\/]/)[0]))
if (tops.size === 1 && listed.some((e) => /[\\/]/.test(e.name))) {
const [top] = tops
if (/[A-Za-z]{2,}.*[._-].*[A-Za-z0-9]{2,}/.test(top) && !looksRandom(top)) return top
}
@@ -129,6 +131,36 @@ export function releaseNameFromContents(info: Pick<RarInfo, 'entries'>): string
return candidates[0]?.stem
}
/** An entry in a hidden directory or a hidden file itself (`.x/…`, `dir/.x`). */
export function isHidden(name: string): boolean {
return name.split(/[\\/]/).some((part) => part.startsWith('.'))
}
/**
* Releases named after a hidden archive entry before those were skipped:
* renamed from their stored listing (no downloads). Used by
* `release:rename --hidden`.
*/
export async function renameHiddenNamed(): Promise<number> {
const releases = await db
.from('releases')
.where('name', 'like', '.%')
.whereNot('source', 'spot')
.whereNotNull('contents')
.select('id', 'name', 'contents')
let renamed = 0
for (const release of releases) {
const entries: any[] = Array.isArray(release.contents) ? release.contents : []
const fromHidden = entries.some((e) => isHidden(e.name) && fileStem(String(e.name).split(/[\\/]/).pop() ?? '') === release.name)
if (!fromHidden) continue
const better = releaseNameFromContents({ entries })
if (!better || better === release.name) continue
await db.from('releases').where('id', release.id).update({ name: better })
renamed++
}
return renamed
}
/**
* Renames random-named releases from their stored archive listing (no
* downloads). Used by `release:rename`.
+9 -2
View File
@@ -1,6 +1,6 @@
import { BaseCommand } from '@adonisjs/core/ace'
import { BaseCommand, flags } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
import { renameFromStoredContents } from '#services/ArchiveService'
import { renameFromStoredContents, renameHiddenNamed } from '#services/ArchiveService'
import { regroupAndRenameFromPar2 } from '#services/NameService'
/** Renames random-named releases from their stored archive listings (no downloads). */
@@ -12,7 +12,14 @@ export default class ReleaseRename extends BaseCommand {
startApp: true,
}
@flags.boolean({ description: 'Only releases named after a hidden archive entry (e.g. a decoy ".x.mkv")' })
declare hidden: boolean
public async run() {
if (this.hidden) {
this.logger.info(`Renamed ${await renameHiddenNamed()} releases named after a hidden archive entry.`)
return
}
const par2 = await regroupAndRenameFromPar2()
this.logger.info(`par2: ${par2.regrouped} files moved into the set that names them, ${par2.renamed} releases renamed.`)
this.logger.info(`Renamed ${await renameFromStoredContents()} releases from their archive contents.`)
+13
View File
@@ -211,6 +211,19 @@ test.group('rar / release naming from contents', () => {
assert.isUndefined(releaseNameFromContents(info(['Xq8r2KdLm3Zw91Pq/x.mkv'])))
})
test('hidden entries (a decoy) name nothing', async ({ assert }) => {
const { releaseNameFromContents, isHidden } = await import('#services/ArchiveService')
const decoy = '.1917-2019-2160p_MA_WEB-DL_Hybrid_H265_DV_HDR_DDP_Atmos_5.1_English-HONE-.mkv'
const listing = info([decoy, 'Boston_Blue_S01E02_1080p_AMZN_WEB-DL_DDP5_1_H_264-FLUX', `${decoy}/${decoy}.19OMnL`, 'Boston_Blue_S01E02_1080p_AMZN_WEB-DL_DDP5_1_H_264-FLUX/boston.blue.s01e02.rar'])
listing.entries[0].directory = true
listing.entries[1].directory = true
listing.entries[2].size = 22_093_429_730
assert.equal(releaseNameFromContents(listing), 'Boston_Blue_S01E02_1080p_AMZN_WEB-DL_DDP5_1_H_264-FLUX')
assert.isTrue(isHidden(`${decoy}/x.mkv`))
assert.isTrue(isHidden('dir/.hidden.mkv'))
assert.isFalse(isHidden('Some.Release/movie.mkv'))
})
test('recognises obfuscated names', async ({ assert }) => {
const { looksRandom } = await import('#services/ArchiveService')
assert.isTrue(looksRandom('CGBWgdhlSCAOAW62gLWRRM'))