@ctrl/sabnzbd 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 +207 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/normalizeUsenetData.d.ts +7 -0
- package/dist/src/normalizeUsenetData.js +231 -0
- package/dist/src/sabnzbd.d.ts +328 -0
- package/dist/src/sabnzbd.js +774 -0
- package/dist/src/types.d.ts +444 -0
- package/dist/src/types.js +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Scott Cooper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# SABnzbd
|
|
2
|
+
|
|
3
|
+
> TypeScript api wrapper for [SABnzbd](https://sabnzbd.org/) using [ofetch](https://github.com/unjs/ofetch)
|
|
4
|
+
|
|
5
|
+
### Overview
|
|
6
|
+
|
|
7
|
+
Includes the normalized usenet API shared with `@ctrl/nzbget`:
|
|
8
|
+
|
|
9
|
+
- [`getAllData()`](#getalldata)
|
|
10
|
+
- [`getQueue()`](#getqueue)
|
|
11
|
+
- [`getHistory()`](#gethistory)
|
|
12
|
+
- [`getQueueJob(id)`](#getqueuejobid)
|
|
13
|
+
- [`getHistoryJob(id)`](#gethistoryjobid)
|
|
14
|
+
- [`findJob(id)`](#findjobid)
|
|
15
|
+
- [`addNzbFile(...)` / `addNzbUrl(...)`](#addnzbfile--addnzburl)
|
|
16
|
+
- [`normalizedAddNzb(...)`](#normalizedaddnzb)
|
|
17
|
+
- queue control methods return `boolean`
|
|
18
|
+
- `addNzbFile` and `addNzbUrl` return the normalized queue id as a `string`
|
|
19
|
+
|
|
20
|
+
Use the normalized methods by default. Drop to the native SABnzbd methods only when you need SAB-specific behavior such as script changes, post-process changes, rename operations, or raw queue/history responses.
|
|
21
|
+
|
|
22
|
+
### Install
|
|
23
|
+
|
|
24
|
+
```console
|
|
25
|
+
npm install @ctrl/sabnzbd
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Use
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Sabnzbd } from '@ctrl/sabnzbd';
|
|
32
|
+
|
|
33
|
+
const client = new Sabnzbd({
|
|
34
|
+
baseUrl: 'http://localhost:8080/',
|
|
35
|
+
apiKey: 'api-key',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const data = await client.getAllData();
|
|
40
|
+
console.log(data.queue);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Normalized Example
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { Sabnzbd, UsenetNotFoundError, UsenetPriority } from '@ctrl/sabnzbd';
|
|
48
|
+
|
|
49
|
+
const client = new Sabnzbd({
|
|
50
|
+
baseUrl: 'http://localhost:8080/',
|
|
51
|
+
apiKey: 'api-key',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
const id = await client.addNzbUrl('https://example.test/release.nzb', {
|
|
56
|
+
category: 'movies',
|
|
57
|
+
priority: UsenetPriority.high,
|
|
58
|
+
startPaused: false,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const job = await client.getQueueJob(id);
|
|
63
|
+
console.log(job.state, job.progress);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof UsenetNotFoundError) {
|
|
66
|
+
console.log('job missing', error.id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### API
|
|
73
|
+
|
|
74
|
+
Docs: https://sabnzbd.ep.workers.dev
|
|
75
|
+
SABnzbd API Docs: https://sabnzbd.org/wiki/configuration/4.5/api
|
|
76
|
+
|
|
77
|
+
### Normalized Methods
|
|
78
|
+
|
|
79
|
+
##### `getAllData()`
|
|
80
|
+
|
|
81
|
+
Returns queue, history, categories, scripts, and status in normalized form. This is the broadest normalized read and fits best when you want an overview in one call.
|
|
82
|
+
|
|
83
|
+
##### `getQueue()`
|
|
84
|
+
|
|
85
|
+
Returns normalized active queue items.
|
|
86
|
+
|
|
87
|
+
##### `getHistory()`
|
|
88
|
+
|
|
89
|
+
Returns normalized history items.
|
|
90
|
+
|
|
91
|
+
##### `getQueueJob(id)`
|
|
92
|
+
|
|
93
|
+
Returns one normalized active queue item. Missing ids throw `UsenetNotFoundError`.
|
|
94
|
+
|
|
95
|
+
##### `getHistoryJob(id)`
|
|
96
|
+
|
|
97
|
+
Returns one normalized history item. Missing ids throw `UsenetNotFoundError`.
|
|
98
|
+
|
|
99
|
+
##### `findJob(id)`
|
|
100
|
+
|
|
101
|
+
Searches queue first, then history, and returns `{ source, job }` or `null`. It is the convenient path when you do not know which side the id should be on.
|
|
102
|
+
|
|
103
|
+
##### `addNzbFile(...)` / `addNzbUrl(...)`
|
|
104
|
+
|
|
105
|
+
Add an NZB and return the normalized queue id as a `string`. These are the lighter add helpers when an id is enough.
|
|
106
|
+
The normalized add option names are `category`, `priority`, `postProcess`, `postProcessScript`, `name`, `password`, and `startPaused`.
|
|
107
|
+
|
|
108
|
+
##### `normalizedAddNzb(...)`
|
|
109
|
+
|
|
110
|
+
Add an NZB from either a URL or file content and return the created normalized queue item. This is the higher-level add helper when you want the normalized job back immediately.
|
|
111
|
+
|
|
112
|
+
##### Normalized state labels
|
|
113
|
+
|
|
114
|
+
`stateMessage` uses the shared `UsenetStateMessage` vocabulary:
|
|
115
|
+
`Grabbing`, `Queued`, `Downloading`, `Paused`, `Post-processing`, `Completed`, `Failed`, `Warning`, `Deleted`, and `Unknown`.
|
|
116
|
+
|
|
117
|
+
### Native API
|
|
118
|
+
|
|
119
|
+
SABnzbd-specific methods are still available when you need the raw client surface.
|
|
120
|
+
|
|
121
|
+
Connection and discovery:
|
|
122
|
+
|
|
123
|
+
- `auth()`
|
|
124
|
+
- `getVersion()`
|
|
125
|
+
- `getFullStatus()`
|
|
126
|
+
- `getWarnings()`
|
|
127
|
+
- `clearWarnings()`
|
|
128
|
+
- `getServerStats()`
|
|
129
|
+
- `listQueue(query?)`
|
|
130
|
+
- `listHistory(query?)`
|
|
131
|
+
- `getCategories()`
|
|
132
|
+
- `getScripts()`
|
|
133
|
+
- `getFiles(id)`
|
|
134
|
+
|
|
135
|
+
Queue and job mutation:
|
|
136
|
+
|
|
137
|
+
- `deleteJob(id, deleteFiles?)`
|
|
138
|
+
- `shutdown()`
|
|
139
|
+
- `restart()`
|
|
140
|
+
- `restartRepair()`
|
|
141
|
+
- `pausePostProcessing()`
|
|
142
|
+
- `resumePostProcessing()`
|
|
143
|
+
- `fetchRss()`
|
|
144
|
+
- `scanWatchedFolder()`
|
|
145
|
+
- `resetQuota()`
|
|
146
|
+
- `changeCategory(id, category)`
|
|
147
|
+
- `changeScript(id, script)`
|
|
148
|
+
- `changePriority(id, priority)`
|
|
149
|
+
- `changePostProcess(id, postProcess)`
|
|
150
|
+
- `renameJob(id, name, password?)`
|
|
151
|
+
- `setSpeedLimit(limit)`
|
|
152
|
+
|
|
153
|
+
Raw add methods:
|
|
154
|
+
|
|
155
|
+
- `addUrl(url, options?)`
|
|
156
|
+
- `addFile(nzb, options?)`
|
|
157
|
+
|
|
158
|
+
##### export and create from state
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const state = client.exportState();
|
|
162
|
+
const restored = Sabnzbd.createFromState(config, state);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Local Integration Testing
|
|
166
|
+
|
|
167
|
+
Use a disposable SABnzbd instance on `localhost:8080` with its config mounted at `/tmp/sabnzbd-local-test`.
|
|
168
|
+
|
|
169
|
+
```console
|
|
170
|
+
docker run -d --name sabnzbd-local-test \
|
|
171
|
+
-p 8080:8080 \
|
|
172
|
+
-v /tmp/sabnzbd-local-test:/config \
|
|
173
|
+
lscr.io/linuxserver/sabnzbd:latest
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Wait for first-run setup to create `sabnzbd.ini`:
|
|
177
|
+
|
|
178
|
+
```console
|
|
179
|
+
ls -l /tmp/sabnzbd-local-test/sabnzbd.ini
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Read the generated API key:
|
|
183
|
+
|
|
184
|
+
```console
|
|
185
|
+
docker exec sabnzbd-local-test sed -n 's/^api_key = //p' /config/sabnzbd.ini
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Run only the integration spec:
|
|
189
|
+
|
|
190
|
+
```console
|
|
191
|
+
TEST_SABNZBD_URL=http://127.0.0.1:8080 \
|
|
192
|
+
TEST_SABNZBD_API_KEY=$(docker exec sabnzbd-local-test sed -n 's/^api_key = //p' /config/sabnzbd.ini) \
|
|
193
|
+
pnpm test test/integration.spec.ts
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Run the full test suite:
|
|
197
|
+
|
|
198
|
+
```console
|
|
199
|
+
TEST_SABNZBD_URL=http://127.0.0.1:8080 \
|
|
200
|
+
TEST_SABNZBD_API_KEY=$(docker exec sabnzbd-local-test sed -n 's/^api_key = //p' /config/sabnzbd.ini) \
|
|
201
|
+
pnpm test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The integration spec in [`test/integration.spec.ts`](/Users/scooper/gh/sabnzbd/test/integration.spec.ts) defaults to this exact setup:
|
|
205
|
+
|
|
206
|
+
- `baseUrl` defaults to `http://127.0.0.1:8080`
|
|
207
|
+
- `apiKey` is read from `/tmp/sabnzbd-local-test/sabnzbd.ini` if `TEST_SABNZBD_API_KEY` is unset
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type NormalizedUsenetHistoryItem, type NormalizedUsenetJob, type NormalizedUsenetStatus, UsenetPriority } from '@ctrl/shared-usenet';
|
|
2
|
+
import type { SabFullStatus, SabHistorySlot, SabPriorityValue, SabRawPriorityValue, SabQueue, SabQueueSlot } from './types.js';
|
|
3
|
+
export declare function sabPriorityToNormalized(priority: SabRawPriorityValue | undefined): UsenetPriority;
|
|
4
|
+
export declare function normalizedPriorityToSab(priority: UsenetPriority | undefined): SabPriorityValue;
|
|
5
|
+
export declare function normalizeSabJob(slot: SabQueueSlot): NormalizedUsenetJob;
|
|
6
|
+
export declare function normalizeSabHistoryItem(item: SabHistorySlot): NormalizedUsenetHistoryItem;
|
|
7
|
+
export declare function normalizeSabStatus(queue: SabQueue, fullStatus: SabFullStatus): NormalizedUsenetStatus;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { UsenetJobState, UsenetPriority, UsenetStateMessage, } from '@ctrl/shared-usenet';
|
|
2
|
+
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
|
3
|
+
function toNumber(value) {
|
|
4
|
+
const parsed = Number.parseFloat(String(value ?? '0').replaceAll(',', ''));
|
|
5
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
6
|
+
}
|
|
7
|
+
function megabytesToBytes(value) {
|
|
8
|
+
return Math.round(toNumber(value) * BYTES_PER_MEGABYTE);
|
|
9
|
+
}
|
|
10
|
+
function parseSabDuration(value) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
const parts = value.split(':').map(part => Number.parseInt(part, 10));
|
|
15
|
+
if (parts.some(part => Number.isNaN(part))) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (parts.length === 3) {
|
|
19
|
+
const [hours = 0, minutes = 0, seconds = 0] = parts;
|
|
20
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
21
|
+
}
|
|
22
|
+
if (parts.length === 2) {
|
|
23
|
+
const [minutes = 0, seconds = 0] = parts;
|
|
24
|
+
return minutes * 60 + seconds;
|
|
25
|
+
}
|
|
26
|
+
return parts[0] ?? 0;
|
|
27
|
+
}
|
|
28
|
+
function normalizeIsoDate(value) {
|
|
29
|
+
if (typeof value === 'number' || (typeof value === 'string' && /^\d+$/.test(value))) {
|
|
30
|
+
const numericValue = Number(value);
|
|
31
|
+
if (numericValue > 0) {
|
|
32
|
+
const timestamp = numericValue > 1_000_000_000_000 ? numericValue : numericValue * 1000;
|
|
33
|
+
return new Date(timestamp).toISOString();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === 'string' && value.trim()) {
|
|
37
|
+
const parsed = new Date(value);
|
|
38
|
+
if (!Number.isNaN(parsed.valueOf())) {
|
|
39
|
+
return parsed.toISOString();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
export function sabPriorityToNormalized(priority) {
|
|
45
|
+
const normalized = Number.parseInt(String(priority ?? UsenetPriority.default), 10);
|
|
46
|
+
switch (normalized) {
|
|
47
|
+
case -4: {
|
|
48
|
+
return UsenetPriority.stopped;
|
|
49
|
+
}
|
|
50
|
+
case -3: {
|
|
51
|
+
return UsenetPriority.duplicate;
|
|
52
|
+
}
|
|
53
|
+
case -2: {
|
|
54
|
+
return UsenetPriority.paused;
|
|
55
|
+
}
|
|
56
|
+
case -1: {
|
|
57
|
+
return UsenetPriority.low;
|
|
58
|
+
}
|
|
59
|
+
case 0: {
|
|
60
|
+
return UsenetPriority.normal;
|
|
61
|
+
}
|
|
62
|
+
case 1: {
|
|
63
|
+
return UsenetPriority.high;
|
|
64
|
+
}
|
|
65
|
+
case 2: {
|
|
66
|
+
return UsenetPriority.force;
|
|
67
|
+
}
|
|
68
|
+
case -100: {
|
|
69
|
+
return UsenetPriority.default;
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
return UsenetPriority.default;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function normalizedPriorityToSab(priority) {
|
|
77
|
+
switch (priority) {
|
|
78
|
+
case UsenetPriority.stopped: {
|
|
79
|
+
return -4;
|
|
80
|
+
}
|
|
81
|
+
case UsenetPriority.duplicate: {
|
|
82
|
+
return -3;
|
|
83
|
+
}
|
|
84
|
+
case UsenetPriority.paused: {
|
|
85
|
+
return -2;
|
|
86
|
+
}
|
|
87
|
+
case UsenetPriority.veryLow: {
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
90
|
+
case UsenetPriority.low: {
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
case UsenetPriority.normal: {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
case UsenetPriority.high: {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
case UsenetPriority.veryHigh: {
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
case UsenetPriority.force: {
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
case UsenetPriority.default:
|
|
106
|
+
default: {
|
|
107
|
+
return -100;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function mapSabStatus(status, failMessage = '') {
|
|
112
|
+
switch (status) {
|
|
113
|
+
case 'Grabbing': {
|
|
114
|
+
return { state: UsenetJobState.grabbing, stateMessage: UsenetStateMessage.grabbing };
|
|
115
|
+
}
|
|
116
|
+
case 'Queued': {
|
|
117
|
+
return { state: UsenetJobState.queued, stateMessage: UsenetStateMessage.queued };
|
|
118
|
+
}
|
|
119
|
+
case 'Paused': {
|
|
120
|
+
return { state: UsenetJobState.paused, stateMessage: UsenetStateMessage.paused };
|
|
121
|
+
}
|
|
122
|
+
case 'Downloading': {
|
|
123
|
+
return {
|
|
124
|
+
state: UsenetJobState.downloading,
|
|
125
|
+
stateMessage: UsenetStateMessage.downloading,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case 'Fetching': {
|
|
129
|
+
return {
|
|
130
|
+
state: UsenetJobState.downloading,
|
|
131
|
+
stateMessage: UsenetStateMessage.downloading,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
case 'Propagating': {
|
|
135
|
+
return {
|
|
136
|
+
state: UsenetJobState.downloading,
|
|
137
|
+
stateMessage: UsenetStateMessage.downloading,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
case 'Checking':
|
|
141
|
+
case 'QuickCheck':
|
|
142
|
+
case 'Verifying':
|
|
143
|
+
case 'Repairing':
|
|
144
|
+
case 'Extracting':
|
|
145
|
+
case 'Moving':
|
|
146
|
+
case 'Running': {
|
|
147
|
+
return {
|
|
148
|
+
state: UsenetJobState.postProcessing,
|
|
149
|
+
stateMessage: UsenetStateMessage.postProcessing,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
case 'Completed': {
|
|
153
|
+
return { state: UsenetJobState.completed, stateMessage: UsenetStateMessage.completed };
|
|
154
|
+
}
|
|
155
|
+
case 'Failed': {
|
|
156
|
+
return {
|
|
157
|
+
state: failMessage ? UsenetJobState.error : UsenetJobState.warning,
|
|
158
|
+
stateMessage: failMessage ? UsenetStateMessage.failed : UsenetStateMessage.warning,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
case 'Deleted': {
|
|
162
|
+
return { state: UsenetJobState.deleted, stateMessage: UsenetStateMessage.deleted };
|
|
163
|
+
}
|
|
164
|
+
default: {
|
|
165
|
+
return { state: UsenetJobState.unknown, stateMessage: UsenetStateMessage.unknown };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function normalizeSabJob(slot) {
|
|
170
|
+
const { state, stateMessage } = mapSabStatus(slot.status);
|
|
171
|
+
const totalSize = megabytesToBytes(slot.mb);
|
|
172
|
+
const remainingSize = megabytesToBytes(slot.mbleft);
|
|
173
|
+
const progress = toNumber(slot.percentage);
|
|
174
|
+
return {
|
|
175
|
+
id: slot.nzo_id,
|
|
176
|
+
name: slot.filename,
|
|
177
|
+
progress,
|
|
178
|
+
isCompleted: progress >= 100,
|
|
179
|
+
category: slot.cat || '',
|
|
180
|
+
priority: sabPriorityToNormalized(slot.priority),
|
|
181
|
+
state,
|
|
182
|
+
stateMessage,
|
|
183
|
+
downloadSpeed: 0,
|
|
184
|
+
eta: parseSabDuration(slot.timeleft),
|
|
185
|
+
queuePosition: slot.index,
|
|
186
|
+
totalSize,
|
|
187
|
+
remainingSize,
|
|
188
|
+
savePath: undefined,
|
|
189
|
+
postProcessScript: slot.script,
|
|
190
|
+
raw: slot,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function normalizeSabHistoryItem(item) {
|
|
194
|
+
const { state, stateMessage } = mapSabStatus(item.status, item.fail_message);
|
|
195
|
+
const totalSize = toNumber(item.bytes);
|
|
196
|
+
const succeeded = state === UsenetJobState.completed;
|
|
197
|
+
return {
|
|
198
|
+
id: item.nzo_id,
|
|
199
|
+
name: item.name || item.nzb_name || item.nzo_id,
|
|
200
|
+
progress: succeeded ? 100 : 0,
|
|
201
|
+
isCompleted: succeeded,
|
|
202
|
+
category: item.category || '',
|
|
203
|
+
priority: undefined,
|
|
204
|
+
state,
|
|
205
|
+
stateMessage,
|
|
206
|
+
downloadSpeed: 0,
|
|
207
|
+
eta: 0,
|
|
208
|
+
queuePosition: -1,
|
|
209
|
+
totalSize,
|
|
210
|
+
remainingSize: 0,
|
|
211
|
+
savePath: item.storage,
|
|
212
|
+
dateCompleted: normalizeIsoDate(item.completed),
|
|
213
|
+
postProcessScript: item.script,
|
|
214
|
+
failureMessage: item.fail_message,
|
|
215
|
+
storagePath: item.storage,
|
|
216
|
+
succeeded,
|
|
217
|
+
raw: item,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export function normalizeSabStatus(queue, fullStatus) {
|
|
221
|
+
return {
|
|
222
|
+
isDownloadPaused: Boolean(queue.paused),
|
|
223
|
+
speedBytesPerSecond: Math.round(toNumber(queue.kbpersec) * 1024),
|
|
224
|
+
totalRemainingSize: megabytesToBytes(queue.mbleft),
|
|
225
|
+
completeDir: fullStatus.completedir,
|
|
226
|
+
raw: {
|
|
227
|
+
queue,
|
|
228
|
+
fullStatus,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|