@ctrl/nzbget 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/normalizeUsenetData.d.ts +11 -0
- package/dist/src/normalizeUsenetData.js +227 -0
- package/dist/src/nzbget.d.ts +84 -0
- package/dist/src/nzbget.js +341 -0
- package/dist/src/types.d.ts +669 -0
- package/dist/src/types.js +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { UsenetNotFoundError, } from '@ctrl/shared-usenet';
|
|
2
|
+
import { ofetch } from 'ofetch';
|
|
3
|
+
import { joinURL } from 'ufo';
|
|
4
|
+
import { stringToBase64, stringToUint8Array, uint8ArrayToBase64 } from 'uint8array-extras';
|
|
5
|
+
import { configItemsToMap, deriveCategories, deriveScripts, normalizeNzbgetHistoryItem, normalizeNzbgetJob, normalizeNzbgetStatus, normalizedPriorityToNzbget, } from './normalizeUsenetData.js';
|
|
6
|
+
const defaults = {
|
|
7
|
+
baseUrl: 'http://localhost:6789/',
|
|
8
|
+
path: '/jsonrpc',
|
|
9
|
+
username: 'nzbget',
|
|
10
|
+
password: 'tegbzn6789',
|
|
11
|
+
timeout: 5000,
|
|
12
|
+
};
|
|
13
|
+
function encodeBasicAuth(username, password) {
|
|
14
|
+
return stringToBase64(`${username}:${password}`);
|
|
15
|
+
}
|
|
16
|
+
function normalizeIds(ids) {
|
|
17
|
+
const values = Array.isArray(ids) ? ids : [ids];
|
|
18
|
+
return values.map(value => Number.parseInt(`${value}`, 10)).filter(value => !Number.isNaN(value));
|
|
19
|
+
}
|
|
20
|
+
function toBase64(input) {
|
|
21
|
+
return uint8ArrayToBase64(typeof input === 'string' ? stringToUint8Array(input) : input);
|
|
22
|
+
}
|
|
23
|
+
async function sleep(milliseconds) {
|
|
24
|
+
await new Promise(resolve => {
|
|
25
|
+
setTimeout(resolve, milliseconds);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export class Nzbget {
|
|
29
|
+
static createFromState(config, state) {
|
|
30
|
+
const client = new Nzbget(config);
|
|
31
|
+
client.state = { ...state };
|
|
32
|
+
return client;
|
|
33
|
+
}
|
|
34
|
+
config;
|
|
35
|
+
state = {};
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.config = { ...defaults, ...options };
|
|
38
|
+
}
|
|
39
|
+
exportState() {
|
|
40
|
+
return JSON.parse(JSON.stringify(this.state));
|
|
41
|
+
}
|
|
42
|
+
/** Calls {@link https://nzbget-ng.github.io/api/version | version}. */
|
|
43
|
+
async getVersion() {
|
|
44
|
+
const version = await this.rpc('version');
|
|
45
|
+
this.state.version = { version };
|
|
46
|
+
return version;
|
|
47
|
+
}
|
|
48
|
+
/** Calls {@link https://nzbget-ng.github.io/api/shutdown | shutdown}. */
|
|
49
|
+
async shutdown() {
|
|
50
|
+
return this.rpc('shutdown');
|
|
51
|
+
}
|
|
52
|
+
/** Calls {@link https://nzbget-ng.github.io/api/reload | reload}. */
|
|
53
|
+
async reload() {
|
|
54
|
+
return this.rpc('reload');
|
|
55
|
+
}
|
|
56
|
+
/** Calls {@link https://nzbget-ng.github.io/api/status | status}. */
|
|
57
|
+
async status() {
|
|
58
|
+
return this.rpc('status');
|
|
59
|
+
}
|
|
60
|
+
/** Calls {@link https://nzbget-ng.github.io/api/listgroups | listgroups}. */
|
|
61
|
+
async listGroups() {
|
|
62
|
+
return this.rpc('listgroups', [0]);
|
|
63
|
+
}
|
|
64
|
+
/** Calls {@link https://nzbget-ng.github.io/api/history | history}. */
|
|
65
|
+
async history(hidden = false) {
|
|
66
|
+
return this.rpc('history', [hidden]);
|
|
67
|
+
}
|
|
68
|
+
/** Calls {@link https://nzbget-ng.github.io/api/config | config}. */
|
|
69
|
+
async getConfig() {
|
|
70
|
+
const items = await this.rpc('config');
|
|
71
|
+
return configItemsToMap(items);
|
|
72
|
+
}
|
|
73
|
+
/** Calls {@link https://nzbget-ng.github.io/api/configtemplates | configtemplates}. */
|
|
74
|
+
async configTemplates(loadFromDisk = false) {
|
|
75
|
+
return this.rpc('configtemplates', [loadFromDisk]);
|
|
76
|
+
}
|
|
77
|
+
/** Calls {@link https://nzbget-ng.github.io/api/listfiles | listfiles}. */
|
|
78
|
+
async listFiles(id) {
|
|
79
|
+
return this.rpc('listfiles', [0, 0, Number.parseInt(`${id}`, 10)]);
|
|
80
|
+
}
|
|
81
|
+
/** Calls {@link https://nzbget-ng.github.io/api/pausedownload | pausedownload}. */
|
|
82
|
+
async pauseDownload() {
|
|
83
|
+
return this.rpc('pausedownload');
|
|
84
|
+
}
|
|
85
|
+
/** Calls {@link https://nzbget-ng.github.io/api/resumedownload | resumedownload}. */
|
|
86
|
+
async resumeDownload() {
|
|
87
|
+
return this.rpc('resumedownload');
|
|
88
|
+
}
|
|
89
|
+
/** Calls {@link https://nzbget-ng.github.io/api/pausepost | pausepost}. */
|
|
90
|
+
async pausePost() {
|
|
91
|
+
return this.rpc('pausepost');
|
|
92
|
+
}
|
|
93
|
+
/** Calls {@link https://nzbget-ng.github.io/api/resumepost | resumepost}. */
|
|
94
|
+
async resumePost() {
|
|
95
|
+
return this.rpc('resumepost');
|
|
96
|
+
}
|
|
97
|
+
/** Calls {@link https://nzbget-ng.github.io/api/pausescan | pausescan}. */
|
|
98
|
+
async pauseScan() {
|
|
99
|
+
return this.rpc('pausescan');
|
|
100
|
+
}
|
|
101
|
+
/** Calls {@link https://nzbget-ng.github.io/api/resumescan | resumescan}. */
|
|
102
|
+
async resumeScan() {
|
|
103
|
+
return this.rpc('resumescan');
|
|
104
|
+
}
|
|
105
|
+
/** Calls {@link https://nzbget-ng.github.io/api/scheduleresume | scheduleresume}. */
|
|
106
|
+
async scheduleResume(seconds) {
|
|
107
|
+
return this.rpc('scheduleresume', [seconds]);
|
|
108
|
+
}
|
|
109
|
+
/** Calls {@link https://nzbget-ng.github.io/api/rate | rate}. */
|
|
110
|
+
async setRate(limitBytesPerSecond) {
|
|
111
|
+
return this.rpc('rate', [limitBytesPerSecond]);
|
|
112
|
+
}
|
|
113
|
+
/** Calls {@link https://nzbget-ng.github.io/api/append | append}. */
|
|
114
|
+
async append(name, contentOrUrl, options = {}) {
|
|
115
|
+
return this.rpc('append', [
|
|
116
|
+
name,
|
|
117
|
+
contentOrUrl,
|
|
118
|
+
options.category ?? '',
|
|
119
|
+
options.priority ?? 0,
|
|
120
|
+
options.addToTop ?? false,
|
|
121
|
+
options.addPaused ?? false,
|
|
122
|
+
options.dupeKey ?? '',
|
|
123
|
+
options.dupeScore ?? 0,
|
|
124
|
+
options.dupeMode ?? 'all',
|
|
125
|
+
options.ppParameters ?? [],
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
/** Calls {@link https://nzbget-ng.github.io/api/editqueue | editqueue}. */
|
|
129
|
+
async editQueue(command, parameter, ids) {
|
|
130
|
+
const normalizedIds = normalizeIds(ids);
|
|
131
|
+
try {
|
|
132
|
+
return await this.rpc('editqueue', [command, `${parameter}`, normalizedIds]);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return this.rpc('editqueue', [command, 0, `${parameter}`, normalizedIds]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Calls {@link https://nzbget-ng.github.io/api/scan | scan}. */
|
|
139
|
+
async scan() {
|
|
140
|
+
return this.rpc('scan');
|
|
141
|
+
}
|
|
142
|
+
/** Calls {@link https://nzbget-ng.github.io/api/log | log}. */
|
|
143
|
+
async log(idFrom, numberOfEntries) {
|
|
144
|
+
return this.rpc('log', [idFrom, numberOfEntries]);
|
|
145
|
+
}
|
|
146
|
+
/** Calls {@link https://nzbget-ng.github.io/api/writelog | writelog}. */
|
|
147
|
+
async writeLog(kind, text) {
|
|
148
|
+
return this.rpc('writelog', [kind, text]);
|
|
149
|
+
}
|
|
150
|
+
/** Calls {@link https://nzbget-ng.github.io/api/loadlog | loadlog}. */
|
|
151
|
+
async loadLog(nzbId, idFrom, numberOfEntries) {
|
|
152
|
+
return this.rpc('loadlog', [
|
|
153
|
+
Number.parseInt(`${nzbId}`, 10),
|
|
154
|
+
idFrom,
|
|
155
|
+
numberOfEntries,
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
/** Calls {@link https://nzbget-ng.github.io/api/servervolumes | servervolumes}. */
|
|
159
|
+
async serverVolumes() {
|
|
160
|
+
return this.rpc('servervolumes');
|
|
161
|
+
}
|
|
162
|
+
async getCategories() {
|
|
163
|
+
return deriveCategories(await this.getConfig());
|
|
164
|
+
}
|
|
165
|
+
async getScripts() {
|
|
166
|
+
return deriveScripts(await this.configTemplates());
|
|
167
|
+
}
|
|
168
|
+
async pauseQueue() {
|
|
169
|
+
return this.pauseDownload();
|
|
170
|
+
}
|
|
171
|
+
async resumeQueue() {
|
|
172
|
+
return this.resumeDownload();
|
|
173
|
+
}
|
|
174
|
+
async pauseJob(id) {
|
|
175
|
+
return this.editQueue('GroupPause', '', id);
|
|
176
|
+
}
|
|
177
|
+
async resumeJob(id) {
|
|
178
|
+
return this.editQueue('GroupResume', '', id);
|
|
179
|
+
}
|
|
180
|
+
async removeJob(id, removeData = false) {
|
|
181
|
+
return this.editQueue(removeData ? 'GroupFinalDelete' : 'GroupDelete', '', id);
|
|
182
|
+
}
|
|
183
|
+
async moveJob(id, position) {
|
|
184
|
+
const queue = await this.listGroups();
|
|
185
|
+
const rawId = Number.parseInt(id, 10);
|
|
186
|
+
const currentIndex = queue.findIndex(item => item.NZBID === rawId);
|
|
187
|
+
if (currentIndex === -1) {
|
|
188
|
+
throw new UsenetNotFoundError('nzbget', 'queueJob', id);
|
|
189
|
+
}
|
|
190
|
+
if (!Number.isFinite(position)) {
|
|
191
|
+
throw new TypeError('Invalid queue position');
|
|
192
|
+
}
|
|
193
|
+
if (position <= 0) {
|
|
194
|
+
return this.editQueue('GroupMoveTop', '', id);
|
|
195
|
+
}
|
|
196
|
+
if (position >= queue.length - 1) {
|
|
197
|
+
return this.editQueue('GroupMoveBottom', '', id);
|
|
198
|
+
}
|
|
199
|
+
const offset = position - currentIndex;
|
|
200
|
+
if (offset === 0) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return this.editQueue('GroupMoveOffset', offset, id);
|
|
204
|
+
}
|
|
205
|
+
async setCategory(id, category) {
|
|
206
|
+
return this.editQueue('GroupApplyCategory', category, id);
|
|
207
|
+
}
|
|
208
|
+
async setPriority(id, priority) {
|
|
209
|
+
return this.editQueue('GroupSetPriority', normalizedPriorityToNzbget(priority), id);
|
|
210
|
+
}
|
|
211
|
+
async addNzbFile(nzb, options = {}) {
|
|
212
|
+
const id = await this.append(options.name ?? 'upload.nzb', toBase64(nzb), {
|
|
213
|
+
category: options.category ?? '',
|
|
214
|
+
priority: normalizedPriorityToNzbget(options.priority),
|
|
215
|
+
addPaused: options.startPaused ?? false,
|
|
216
|
+
});
|
|
217
|
+
return `${id}`;
|
|
218
|
+
}
|
|
219
|
+
async addNzbUrl(url, options = {}) {
|
|
220
|
+
const id = await this.append(options.name ?? url, url, {
|
|
221
|
+
category: options.category ?? '',
|
|
222
|
+
priority: normalizedPriorityToNzbget(options.priority),
|
|
223
|
+
addPaused: options.startPaused ?? false,
|
|
224
|
+
});
|
|
225
|
+
return `${id}`;
|
|
226
|
+
}
|
|
227
|
+
async getQueue() {
|
|
228
|
+
const [status, groups] = await Promise.all([this.status(), this.listGroups()]);
|
|
229
|
+
return groups.map((item, index) => normalizeNzbgetJob(item, status, index));
|
|
230
|
+
}
|
|
231
|
+
async getHistory() {
|
|
232
|
+
const history = await this.history();
|
|
233
|
+
return history.map(normalizeNzbgetHistoryItem);
|
|
234
|
+
}
|
|
235
|
+
async getQueueJob(id) {
|
|
236
|
+
const rawId = Number.parseInt(id, 10);
|
|
237
|
+
const [status, groups] = await Promise.all([this.status(), this.listGroups()]);
|
|
238
|
+
const index = groups.findIndex(group => group.NZBID === rawId);
|
|
239
|
+
const group = index === -1 ? undefined : groups[index];
|
|
240
|
+
if (!group) {
|
|
241
|
+
throw new UsenetNotFoundError('nzbget', 'queueJob', id);
|
|
242
|
+
}
|
|
243
|
+
return normalizeNzbgetJob(group, status, index);
|
|
244
|
+
}
|
|
245
|
+
async getHistoryJob(id) {
|
|
246
|
+
const rawId = Number.parseInt(id, 10);
|
|
247
|
+
const history = await this.history();
|
|
248
|
+
const historyItem = history.find(item => item.ID === rawId);
|
|
249
|
+
if (!historyItem) {
|
|
250
|
+
throw new UsenetNotFoundError('nzbget', 'historyJob', id);
|
|
251
|
+
}
|
|
252
|
+
return normalizeNzbgetHistoryItem(historyItem);
|
|
253
|
+
}
|
|
254
|
+
async findJob(id) {
|
|
255
|
+
const rawId = Number.parseInt(id, 10);
|
|
256
|
+
const [status, groups] = await Promise.all([this.status(), this.listGroups()]);
|
|
257
|
+
const index = groups.findIndex(group => group.NZBID === rawId);
|
|
258
|
+
const group = index === -1 ? undefined : groups[index];
|
|
259
|
+
if (group) {
|
|
260
|
+
return {
|
|
261
|
+
source: 'queue',
|
|
262
|
+
job: normalizeNzbgetJob(group, status, index),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const history = await this.history();
|
|
266
|
+
const historyItem = history.find(item => item.ID === rawId);
|
|
267
|
+
if (historyItem) {
|
|
268
|
+
return {
|
|
269
|
+
source: 'history',
|
|
270
|
+
job: normalizeNzbgetHistoryItem(historyItem),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
async getAllData() {
|
|
276
|
+
const [status, groups, history, categories, scripts] = await Promise.all([
|
|
277
|
+
this.status(),
|
|
278
|
+
this.listGroups(),
|
|
279
|
+
this.history(),
|
|
280
|
+
this.getCategories(),
|
|
281
|
+
this.getScripts(),
|
|
282
|
+
]);
|
|
283
|
+
return {
|
|
284
|
+
categories,
|
|
285
|
+
scripts,
|
|
286
|
+
queue: groups.map((item, index) => normalizeNzbgetJob(item, status, index)),
|
|
287
|
+
history: history.map(normalizeNzbgetHistoryItem),
|
|
288
|
+
status: normalizeNzbgetStatus(status),
|
|
289
|
+
raw: {
|
|
290
|
+
status,
|
|
291
|
+
groups,
|
|
292
|
+
history,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async normalizedAddNzb(input, options = {}) {
|
|
297
|
+
const id = 'url' in input
|
|
298
|
+
? await this.addNzbUrl(input.url, options)
|
|
299
|
+
: await this.addNzbFile(input.file, options);
|
|
300
|
+
const rawId = Number.parseInt(id, 10);
|
|
301
|
+
if (rawId <= 0) {
|
|
302
|
+
throw new Error('NZBGet did not return a queue id');
|
|
303
|
+
}
|
|
304
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
305
|
+
const [status, groups] = await Promise.all([this.status(), this.listGroups()]);
|
|
306
|
+
const index = groups.findIndex(group => group.NZBID === rawId);
|
|
307
|
+
const group = index === -1 ? undefined : groups[index];
|
|
308
|
+
if (group) {
|
|
309
|
+
return normalizeNzbgetJob(group, status, index);
|
|
310
|
+
}
|
|
311
|
+
await sleep(250);
|
|
312
|
+
}
|
|
313
|
+
throw new Error('Unable to load newly added NZBGet job');
|
|
314
|
+
}
|
|
315
|
+
async rpc(method, params = []) {
|
|
316
|
+
const url = joinURL(this.config.baseUrl, this.config.path ?? '/jsonrpc');
|
|
317
|
+
const username = this.config.username ?? '';
|
|
318
|
+
const password = this.config.password ?? '';
|
|
319
|
+
const headers = username || password
|
|
320
|
+
? {
|
|
321
|
+
Authorization: `Basic ${encodeBasicAuth(username, password)}`,
|
|
322
|
+
}
|
|
323
|
+
: undefined;
|
|
324
|
+
const response = await ofetch(url, {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
headers,
|
|
327
|
+
body: {
|
|
328
|
+
jsonrpc: '2.0',
|
|
329
|
+
method,
|
|
330
|
+
params,
|
|
331
|
+
id: Date.now(),
|
|
332
|
+
},
|
|
333
|
+
dispatcher: this.config.dispatcher,
|
|
334
|
+
timeout: this.config.timeout,
|
|
335
|
+
});
|
|
336
|
+
if (response.error) {
|
|
337
|
+
throw new Error(response.error.message);
|
|
338
|
+
}
|
|
339
|
+
return response.result;
|
|
340
|
+
}
|
|
341
|
+
}
|