Search and the web interface use the prober's findings
Test and deploy / test (push) Successful in 48s
Test and deploy / deploy (push) Successful in 2m27s

- Search matches a word in the name or the inner name (trigram index on
  inner_name, partial, built concurrently); "Readable names" includes
  releases with an inner name.
- unlocked=1 (chip "No password") leaves out passworded releases.
- Results carry passworded and innerName: a Password badge, the inner
  name under the release name.
- The release view: Inside and Inspected facts, a passworded notice, and
  Contents (per probed article its yEnc name, kind, type, encryption and
  the names inside) from release_inspections.
- Newznab: the password attribute from the prober; an obfuscated release
  with an inner name is included, titled by it (without its extension).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-11 19:33:23 +02:00
1 parent 5ab5399b43
commit 92480174b9
8 files changed
+177 -16

No files matched your search

+44 -4
View File
@@ -2,9 +2,16 @@
// text is rendered with textContent, never as HTML.
const $ = (selector) => document.querySelector(selector)
const FILTERS = ['readable', 'complete']
const FILTERS = ['readable', 'complete', 'unlocked']
/** Text inputs that narrow a search: size bounds in MB. */
const FIELDS = ['min_size', 'max_size']
/** What a probed article was (probe_candidates.kind). */
const KIND_LABELS = {
archive: 'Archive start',
par2: 'Par2 index',
lone: 'Single file',
'7z-tail': '7z end',
}
const SOURCE_LABELS = {
stem: 'Filename',
subject: 'Subject',
@@ -37,6 +44,7 @@ const state = {
max_size: '',
readable: false,
complete: false,
unlocked: false,
page: 1,
release: null,
}
@@ -200,12 +208,19 @@ function releaseRow(release) {
release.groups?.length ? release.groups[0] + (release.groups.length > 1 ? ` +${release.groups.length - 1}` : '') : '',
].filter(Boolean)
row.querySelector('.release-meta').textContent = meta.join(' · ')
if (release.innerName) {
const inner = row.querySelector('.release-inner')
inner.textContent = release.innerName
inner.title = 'Found inside by reading its articles'
inner.hidden = false
}
const badges = row.querySelector('.badges')
const files = `${release.fileCount} ${release.fileCount === 1 ? 'file' : 'files'}`
if (release.complete) badges.append(badge(files, 'good', 'Complete'))
else if (release.fileTotal) badges.append(badge(`${release.fileCount}/${release.fileTotal}`, 'warn', 'Files present / announced'))
else badges.append(badge(files))
if (release.passworded) badges.append(badge('Password', 'bad', 'Its archive is encrypted: a password is needed'))
if (!release.settled) badges.append(badge('Collecting', 'accent', 'Recently seen: more files may still arrive'))
if (release.category && release.category !== 8010) badges.append(badge(categoryLabel(release.category), '', 'Category (from the name)'))
if (release.source && release.source !== 'stem' && release.source !== 'subject') {
@@ -323,15 +338,22 @@ function renderDetail(release) {
fact('Posted', release.postedAt ? `${new Date(release.postedAt).toLocaleString()} (${formatAge(release.postedAt)})` : null),
fact('Poster', poster),
fact('Groups', release.groups?.join(', ')),
fact('Indexed', release.firstSeenAt ? `${new Date(release.firstSeenAt).toLocaleString()} (${formatAge(release.firstSeenAt)})` : null)
fact('Indexed', release.firstSeenAt ? `${new Date(release.firstSeenAt).toLocaleString()} (${formatAge(release.firstSeenAt)})` : null),
fact('Inside', release.innerName),
fact('Inspected', release.inspectedAt ? `${new Date(release.inspectedAt).toLocaleString()} (${formatAge(release.inspectedAt)})` : null)
)
const notices = []
if (!release.settledAt) {
notices.push(el('div', { class: 'notice notice-warn', text: 'Recently seen: more files of this release may still arrive. The NZB has what is indexed so far.' }))
}
if (!release.readable) {
notices.push(el('div', { class: 'notice', text: 'Obfuscated file names: what this release holds is only known once its articles are read (phase 2).' }))
if (release.passworded) {
notices.push(el('div', { class: 'notice notice-bad', text: 'Passworded: its archive has encrypted entries or headers. The password is not in the index.' }))
}
if (!release.readable && !release.innerName) {
notices.push(el('div', { class: 'notice', text: release.inspectedAt
? 'Obfuscated file names, and its articles did not say more.'
: 'Obfuscated file names: what this release holds is only known once its articles are read (phase 2).' }))
}
const files = el('table', { class: 'file-table' },
@@ -341,10 +363,28 @@ function renderDetail(release) {
el('td', { class: 'num', text: formatSize(f.size) }),
el('td', { class: 'num', text: `${f.parts} ${f.parts === 1 ? 'part' : 'parts'}` })))))
const inspections = release.inspections ?? []
const contents = inspections.length
? el('div', { class: 'contents' }, inspections.map((i) =>
el('div', { class: 'inspection' },
el('div', { class: 'inspection-head' },
el('span', { class: 'name', text: i.yencName || '(no name)' }),
badge(KIND_LABELS[i.kind] ?? i.kind),
i.type ? badge(i.type) : null,
i.passworded ? badge('Encrypted', 'bad') : null),
i.names?.length
? el('ul', { class: 'inspection-names' }, i.names.map((n) => el('li', { text: n })))
: null,
i.entries > (i.names?.length ?? 0)
? el('small', { text: `${number.format(i.entries)} in all; the largest shown` })
: null)))
: null
$('#detail').replaceChildren(
actions,
...notices,
facts,
...(contents ? section('Contents', `${inspections.length} ${inspections.length === 1 ? 'article' : 'articles'} read`, contents) : []),
...section('Files', `${release.files.length} posted`, files)
)
}