Spotnet (CONCEPT D29): - Index free.pt fully and import spots with their descriptions, categories and NZBs; a spot renames its release, since Spotnet names are more trustworthy than names derived from subjects. - Find passwords in spot descriptions too, and tell "needs a password we have" from "needs a password we don't have". Content inspection: - List the contents of 7z (.7z.001) archives, SFV sets, nicovideo uploads and other archive-like files; mark yEncBin .bin containers as encrypted. - Take real names from yEnc headers for randomly named files, and merge uploads that were split across posts. Storage (D31, D32): - Store segment lists zstd-compressed behind the FileStore accessor. - group:backfill indexes older headers at low priority and pauses when the NVMe or HDD part of the database gets too large. Grouping (D35, D36): - Lone header segments wait in staged_segments until a second segment of their file arrives, which drops the per-article junk binaries. - Counter grouping joins random-subject uploads by poster, [i/M] counter, part count and day. Search and maintenance (D33, D34): - A trigram-indexed search_text column, smaller indexes, estimated stats. - Passes that clean up nonsense names; weekly index maintenance; GC budgets per kind. - Web UI: grey out stale results while a search runs and cancel superseded requests. Count only up to 1,000 matches for the page, which made filter clicks take seconds (Newznab keeps the 10,000 cap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55 lines
3.0 KiB
TypeScript
55 lines
3.0 KiB
TypeScript
import { test } from '@japa/runner'
|
|
import yencode from 'yencode'
|
|
import { sniffContentType } from '#lib/content/index'
|
|
import { crc32Hex, decodeSegment, YencError } from '#lib/yenc/index'
|
|
|
|
test.group('content / sniffContentType', () => {
|
|
test('recognises containers and media by magic bytes', ({ assert }) => {
|
|
const magic = (hex: string, pad = 16) => Buffer.concat([Buffer.from(hex, 'hex'), Buffer.alloc(pad)])
|
|
assert.equal(sniffContentType(Buffer.concat([Buffer.from('nzbe'), Buffer.alloc(40)])), 'nzbe')
|
|
assert.equal(sniffContentType(magic('5041523200504b54')), 'par2')
|
|
assert.equal(sniffContentType(magic('526172211a070100')), 'rar5')
|
|
assert.equal(sniffContentType(magic('526172211a0700')), 'rar4')
|
|
assert.equal(sniffContentType(magic('1a45dfa3')), 'matroska')
|
|
assert.equal(sniffContentType(magic('0000001866747970')), 'mp4')
|
|
assert.equal(sniffContentType(magic('28b52ffd')), 'zstd')
|
|
assert.equal(sniffContentType(Buffer.from('<?xml version="1.0"?>\n<!DOCTYPE nzb PUBLIC "x">')), 'nzb')
|
|
assert.isNull(sniffContentType(Buffer.from('plain text')))
|
|
assert.equal(sniffContentType(magic('ffd8ffe0')), 'jpeg')
|
|
assert.equal(sniffContentType(magic('89504e470d0a1a0a')), 'png')
|
|
assert.equal(sniffContentType(Buffer.concat([Buffer.from('ID3'), Buffer.alloc(16)])), 'mp3')
|
|
assert.equal(sniffContentType(Buffer.concat([Buffer.from('fLaC'), Buffer.alloc(16)])), 'flac')
|
|
assert.equal(sniffContentType(Buffer.from('RIFF\x00\x00\x00\x00AVI LIST', 'latin1')), 'avi')
|
|
|
|
// UDF (Blu-ray) volume recognition sequence at 32 KiB
|
|
const udf = Buffer.alloc(0x9000)
|
|
udf.write('BEA01', 0x8001, 'latin1')
|
|
udf.write('NSR03', 0x8801, 'latin1')
|
|
assert.equal(sniffContentType(udf), 'udf')
|
|
})
|
|
})
|
|
|
|
test.group('yenc / lenientSize', () => {
|
|
test("recognises yEncBin's encrypted .bin container by its header", ({ assert }) => {
|
|
// The first 48 bytes of 2141406641.bin (yEncBin@Poster.com).
|
|
const data = Buffer.from('54f399d117c492696e4dc4ef5b60c11d7f83e92dfdf23450e79370b048123da81e4c8013091b682fb22478daed2ae981', 'hex')
|
|
assert.equal(sniffContentType(data), 'yencbin')
|
|
// The second variant (1060238685.bin).
|
|
assert.equal(sniffContentType(Buffer.from('54f399e917c492696a4e9fb05b60c31d7f83e92df9f16f5086e3c3d9480d66ef1e48d915', 'hex')), 'yencbin')
|
|
assert.isNull(sniffContentType(Buffer.from('54f399d117c492696e4dc4ef5b60c11e7f83e92d', 'hex')))
|
|
})
|
|
|
|
test('decodes a part whose declared size leaves out a header, still checking the CRC', ({ assert }) => {
|
|
const data = Buffer.concat([Buffer.from('nzbe'), Buffer.alloc(16, 7), Buffer.from('ciphertext-here')])
|
|
const declared = data.length - 20
|
|
const body = Buffer.from(
|
|
`=ybegin part=1 line=128 size=${declared} name=f6dd6b156692a52b.bin\r\n=ypart begin=1 end=${declared}\r\n` +
|
|
yencode.encode(data).toString('latin1') +
|
|
`\r\n=yend size=${declared} part=1 pcrc32=${crc32Hex(data)}`,
|
|
'latin1'
|
|
)
|
|
assert.throws(() => decodeSegment(body), YencError as any)
|
|
assert.isTrue(decodeSegment(body, { lenientSize: true }).data.equals(data))
|
|
})
|
|
})
|