- /api (Newznab: caps, search, tvsearch, movie, music, book, get) for Prowlarr, Sonarr and Radarr: voyager 1's controller and XML on the new catalog; readable releases not known to miss files; NEWZNAB_API_KEY. - /api/v1/releases (q, cat, readable, complete, source, poster, size, sort, paging), /api/v1/releases/:id with its files (decoded from the chunks; NzbService.files, shared with the NZB), /api/v1/stats (estimates, plus history and tail progress), /health. Open, as voyager 1's catalog; the web interface uses it. - release_category(name): voyager 1's categorizer without the archive contents, a generated column with a (category, posted_at) index. - @adonisjs/static for the web interface (next). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
import db from '@adonisjs/lucid/services/db'
|
|
import { decodeChunk, type ChunkFile } from '#lib/chunks/index'
|
|
import { buildNzb } from '#lib/nzb/index'
|
|
|
|
/**
|
|
* A release's files and NZB (REBUILD.md, part 9), from its chunks in the
|
|
* chunk store: every chunk decoded, its files in name order. A file stored
|
|
* twice (the same name and first article, e.g. a range read again) is taken
|
|
* once.
|
|
*/
|
|
class NzbService {
|
|
async files(releaseId: number): Promise<ChunkFile[]> {
|
|
const chunks = await db.from('release_chunks').where('release_id', releaseId).orderBy('id').select('payload')
|
|
const files = new Map<string, ChunkFile>()
|
|
for (const chunk of chunks) {
|
|
for (const file of decodeChunk(chunk.payload)) files.set(`${file.name}\n${file.segments[0]?.[1] ?? ''}`, file)
|
|
}
|
|
return [...files.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
}
|
|
|
|
async build(releaseId: number): Promise<{ name: string; xml: string } | null> {
|
|
const release = await db.from('releases').where('id', releaseId).select('name').first()
|
|
if (!release) return null
|
|
const xml = buildNzb(
|
|
(await this.files(releaseId)).map((f) => ({
|
|
name: f.name,
|
|
poster: f.poster,
|
|
date: f.postedAt ?? 0,
|
|
groups: f.groups,
|
|
segments: f.segments.map(([number, id, size]) => ({ number, id, size })),
|
|
})),
|
|
{ title: release.name }
|
|
)
|
|
return { name: release.name, xml }
|
|
}
|
|
}
|
|
|
|
export default new NzbService()
|