Spots: more password phrasings; read listed spots' articles and NZBs
Test and deploy / test (push) Successful in 57s
Test and deploy / deploy (push) Successful in 4m18s

passwordFromSpot finds 30 more passwords in the stored descriptions (480
→ 510): words between keyword and separator, trailing "!!", the value on
the next line, "wachtwoord is x", "… wachtwoord. Deze is: x", "WW x" as a
whole line.

Listed spots were read from their overview only, so the NZB's own
password and the description were unknown for 2.27M of them.
SpotService.readListing reads the spot's article and NZB without
importing files: details, the NZB password (over a spot-text one), the
spot-text password, placeholder NZBs. worker:spots does a batch when
idle; spot:postprocess works through the history (nzb_read_at).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-08 13:46:32 +02:00
1 parent 36f84ae3e6
commit ee898be8c8
8 files changed
+322 -17

No files matched your search

+74
View File
@@ -107,3 +107,77 @@ test.group('SpotService passwords', (group) => {
assert.equal(passwordState(null, false), 'none')
})
})
test.group('SpotService reading listings', (group) => {
group.each.setup(() => testUtils.db().withGlobalTransaction())
const nzb = (password: string | null, bytes = 700_000_000) => `<?xml version="1.0" encoding="UTF-8"?>
<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb"><head>${password ? `<meta type="password">${password}</meta>` : ''}</head>
<file poster="p" date="1700000000" subject="&quot;a.part1.rar&quot; yEnc (1/1)"><groups><group>alt.binaries.test</group></groups>
<segments><segment bytes="${bytes}" number="1">a1@x</segment></segments></file></nzb>`
/** A listed spot, as worker:spots leaves an old one, with its article and NZB as given. */
async function listedSpot(id: string, read: { description?: string; nzb?: string | null; error?: string }) {
await db.table('spots').insert({
message_id: id, group_name: 'free.pt', article_number: 1, title: `Listed ${id}`, poster: 'Bart', key_id: 7, size: 700_000_000,
posted_at: new Date('2019-05-01T12:00:00Z'), verified: true, created_at: new Date(), status: 'pending',
})
const [claimed] = await SpotService.claim(10)
await SpotService.list([claimed])
const service = SpotService as any
service.readHead = async () => {
if (read.error) throw new Error(read.error)
return { header: { size: 700_000_000 }, xml: { description: read.description ?? '', nzbSegments: ['n@x'] } }
}
service.readNzb = async () => read.nzb ?? null
}
const restore = () => {
delete (SpotService as any).readHead
delete (SpotService as any).readNzb
}
group.each.teardown(restore)
const passwordOf = async (id: string) => {
const spot = await db.from('spots').where('message_id', id).first()
const release = await db.from('releases').where('id', spot.release_id).first()
return { spot, password: release.password, source: release.password_source }
}
test("reads the NZB's password and the details, without importing files", async ({ assert }) => {
await listedSpot('l1@spot.net', { description: 'Plot', nzb: nzb('fromnzb1') })
assert.deepEqual(await SpotService.readListings(10, 2), { 'nzb-password': 1 })
const { spot, password, source } = await passwordOf('l1@spot.net')
assert.deepEqual([password, source, spot.status, spot.description], ['fromnzb1', 'nzb', 'listed', 'Plot'])
assert.isNotNull(spot.nzb_read_at)
assert.equal((await db.from('releases').where('id', spot.release_id).first()).file_count, 0)
// Read once: nothing left.
assert.deepEqual(await SpotService.readListings(10, 2), {})
})
test("the spot's text when the NZB has none; the NZB's own password wins over it", async ({ assert }) => {
await listedSpot('l2@spot.net', { description: 'Wachtwoord: fromtext2', nzb: nzb(null) })
assert.deepEqual(await SpotService.readListings(10, 1), { 'spot-password': 1 })
assert.include(await passwordOf('l2@spot.net'), { password: 'fromtext2', source: 'spot' })
await listedSpot('l3@spot.net', { description: 'Wachtwoord: fromtext3', nzb: nzb('fromnzb3') })
await db.from('releases').where('id', (await db.from('spots').where('message_id', 'l3@spot.net').first()).release_id).update({ password: 'fromtext3', password_source: 'spot' })
assert.deepEqual(await SpotService.readListings(10, 1), { 'nzb-password': 1 })
assert.include(await passwordOf('l3@spot.net'), { password: 'fromnzb3', source: 'nzb' })
})
test('a placeholder NZB hides the listing; a lost connection gives the spot back, a gone article does not', async ({ assert }) => {
await listedSpot('l4@spot.net', { nzb: nzb(null, 1500) })
assert.deepEqual(await SpotService.readListings(10, 1), { dummy: 1 })
assert.equal((await db.from('spots').where('message_id', 'l4@spot.net').first()).status, 'dummy')
await listedSpot('l5@spot.net', { error: 'Connection reset' })
assert.deepEqual(await SpotService.readListings(10, 1), { retry: 1 })
assert.isNull((await db.from('spots').where('message_id', 'l5@spot.net').first()).nzb_read_at)
await db.from('spots').where('message_id', 'l5@spot.net').delete()
await listedSpot('l6@spot.net', { error: '430 No Such Article' })
assert.deepEqual(await SpotService.readListings(10, 1), { gone: 1 })
assert.isNotNull((await db.from('spots').where('message_id', 'l6@spot.net').first()).nzb_read_at)
})
})
+21
View File
@@ -146,6 +146,19 @@ test.group('spotnet / passwords in descriptions', () => {
assert.equal(passwordFromSpot('T', '<br />Password = "abc123"<br />'), 'abc123')
})
test('finds passwords in the phrasings found in spots that the first version missed', async ({ assert }) => {
const { passwordFromSpot } = await import('#lib/spotnet/index')
// LOG.md 2026-10-08 (10)
assert.equal(passwordFromSpot('T', 'Formaat: DVD5 ISO[br][br]!! LETOP Wachtwoord : oscfyajg !!'), 'oscfyajg')
assert.equal(passwordFromSpot('T', "Het totaal gewicht komt neer op zo'n 400GB. Deze is voorzien van een wachtwoord. Deze is: VTSm7nLdZZl5Kren[br]"), 'VTSm7nLdZZl5Kren')
assert.equal(passwordFromSpot('T', 'Futurama S11E10 1080P[br][br]ww in nzb: cI6f7dBV6BKkhqD[br][br]Dit was'), 'cI6f7dBV6BKkhqD')
assert.equal(passwordFromSpot('T', '[b]Het wachtwoord voor de nzb is: dw0022[/b][br]'), 'dw0022')
assert.equal(passwordFromSpot('T', 'haakinstructies.[br][br]Wachtwoord inbegrepen: wysiwyg'), 'wysiwyg')
assert.equal(passwordFromSpot('T', 'afgeslagen moeten worden.[br][br]wachtwoord is arn2'), 'arn2')
assert.equal(passwordFromSpot('T', 'Forced : No[br][br]WW:[br]tijdloos[br][br]Zag en merkte'), 'tijdloos')
assert.equal(passwordFromSpot('T', 'telekinetische krachten beschikt.[br]WW carrie[br][br]Multisubs'), 'carrie')
})
test('ignores sentences about passwords', async ({ assert }) => {
const { passwordFromSpot } = await import('#lib/spotnet/index')
for (const description of [
@@ -158,6 +171,14 @@ test.group('spotnet / passwords in descriptions', () => {
'Wachtwoord: geen',
'WW: zie nzb',
'BINSEARCH GEEFT SOMS EEN REQUIRES PASSWORD PROTECT, DIT IS NIET JUIST !!',
'Mijn wachtwoord is hier direct te vinden, je moet het wel zelf overtypen',
'Bringing History to Life - Greatest Battles of WW II, Sept 2026',
'WW II',
'WW Disc 1 = 5YIMKDaoTaKe8WIWEKUF',
'U kunt eigenaars- en gebruikerswachtwoorden aan uw pdf toevoegen met behulp van 256-bits encryptie.',
'APP MISC - SHAREit v6.27.49 ww 4062749 arm64 v8a.apk',
'Het ww staat op het plaatje, type het EXACT zo in.',
'Wachtwoord:[br]Deze download heeft er geen',
]) {
assert.isNull(passwordFromSpot('T', description), description)
}