Hidden archive entries no longer name releases
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:
1 parent
4d3cb6474c
commit
51975fe603
3 files changed
+57
-5
No files matched your search
@@ -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`.
|
||||
|
||||
@@ -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.`)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
Reference in new issue
Block a user