import { create } from 'xmlbuilder2' /** One article of a file: its number, message-id (without <>) and size in bytes. */ export interface Segment { number: number id: string size: number } export interface NzbFile { /** Name the downloader should use; the real name when it is known. */ name: string poster: string /** Posting time in milliseconds. */ date: number groups: string[] segments: Segment[] } export interface NzbOptions { /** Shown by downloaders as the job name. */ title?: string /** Archive password, when known. */ password?: string } /** * Builds an NZB 1.1 document (newzbin DTD). The subject follows the common * `"name" yEnc (1/N)` form, which downloaders use to name the file. */ export function buildNzb(files: NzbFile[], options: NzbOptions = {}): string { const root = create({ version: '1.0', encoding: 'UTF-8' }) .dtd({ pubID: '-//newzBin//DTD NZB 1.1//EN', sysID: 'http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd' }) .ele('nzb', { xmlns: 'http://www.newzbin.com/DTD/2003/nzb' }) if (options.title || options.password) { const head = root.ele('head') if (options.title) head.ele('meta', { type: 'name' }).txt(options.title) if (options.password) head.ele('meta', { type: 'password' }).txt(options.password) } for (const file of files) { const sorted = [...file.segments].sort((a, b) => a.number - b.number) const element = root.ele('file', { poster: file.poster, date: String(Math.floor(file.date / 1000)), subject: `"${file.name}" yEnc (1/${sorted.length})`, }) const groups = element.ele('groups') for (const group of file.groups) groups.ele('group').txt(group) const segments = element.ele('segments') for (const segment of sorted) { segments.ele('segment', { bytes: String(segment.size), number: String(segment.number) }).txt(segment.id) } } return root.end({ prettyPrint: true }) }