diff --git a/public/locales/da/modules/usenet.json b/public/locales/da/modules/usenet.json index 3783e29df..9c481990d 100644 --- a/public/locales/da/modules/usenet.json +++ b/public/locales/da/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Tillader dig at se din Sabnzbd kø og historie, pause og genoptage downloads" + "name": "Usenet", + "description": "Giver dig mulighed for at se din usenet (Sabnzbd eller NZBGet) kø og historik, pause og genoptage downloads" }, "card": { "errors": { diff --git a/public/locales/de/modules/usenet.json b/public/locales/de/modules/usenet.json index 79749c56c..c9be61eff 100644 --- a/public/locales/de/modules/usenet.json +++ b/public/locales/de/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Ermöglicht es Ihnen, Ihre Sabnzbd-Warteschlange und den Verlauf zu sehen, Downloads anzuhalten und fortzusetzen" + "name": "Usenet", + "description": "Ermöglicht es Ihnen, Ihre Usenet-Warteschlange (Sabnzbd oder NZBGet) und den Verlauf anzuzeigen, Downloads anzuhalten und fortzusetzen" }, "card": { "errors": { diff --git a/public/locales/en/layout/add-service-app-shelf.json b/public/locales/en/layout/add-service-app-shelf.json index ca88e1f31..735110906 100644 --- a/public/locales/en/layout/add-service-app-shelf.json +++ b/public/locales/en/layout/add-service-app-shelf.json @@ -90,6 +90,22 @@ "invalidPassword": "Invalid password" } } + }, + "nzbget": { + "username": { + "label": "Username", + "placeholder": "admin", + "validation": { + "invalidUsername": "Invalid username" + } + }, + "password": { + "label": "Password", + "placeholder": "password", + "validation": { + "invalidPassword": "Invalid password" + } + } } } } diff --git a/public/locales/en/modules/usenet.json b/public/locales/en/modules/usenet.json index 1aeec250b..f9df705a3 100644 --- a/public/locales/en/modules/usenet.json +++ b/public/locales/en/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Allows you to see your Sabnzbd queue and history, pause and resume downloads" + "name": "Usenet", + "description": "Allows you to see your usenet (Sabnzbd or NZBGet) queue and history, pause and resume downloads" }, "card": { "errors": { diff --git a/public/locales/es/modules/usenet.json b/public/locales/es/modules/usenet.json index 5f818dfd9..2e830c001 100644 --- a/public/locales/es/modules/usenet.json +++ b/public/locales/es/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Te permite ver tu cola e historial de Sabnzbd, pausar y resumir descargas" + "name": "Usenet", + "description": "Le permite ver la cola y el historial de usenet (Sabnzbd o NZBGet), pausar y reanudar las descargas" }, "card": { "errors": { diff --git a/public/locales/fr/modules/usenet.json b/public/locales/fr/modules/usenet.json index fd9061c24..6a4c0b70c 100644 --- a/public/locales/fr/modules/usenet.json +++ b/public/locales/fr/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Permet de voir votre file d'attente et votre historique Sabnzbd, de mettre en pause et de reprendre les téléchargements" + "name": "Usenet", + "description": "Vous permet de voir votre file d'attente et votre historique Usenet (Sabnzbd ou NZBGet), de mettre en pause et de reprendre les téléchargements" }, "card": { "errors": { diff --git a/public/locales/he/modules/usenet.json b/public/locales/he/modules/usenet.json index e5b26b24f..57a36590c 100644 --- a/public/locales/he/modules/usenet.json +++ b/public/locales/he/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { "name": "Sabnzbd", - "description": "מאפשר צפייה בהיסטוריה ותור ההורדות, עצירה וחידוש הורדות" + "description": "מאמאפשר לך לראות את התור וההיסטוריה של usenet (Sabnzbd או NZBGet), להשהות ולחדש הורדות" }, "card": { "errors": { diff --git a/public/locales/it/modules/usenet.json b/public/locales/it/modules/usenet.json index 728091b8c..27eee650b 100644 --- a/public/locales/it/modules/usenet.json +++ b/public/locales/it/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Permette di vedere la coda e la cronologia di Sabnzbd, di mettere in pausa e di riprendere i download." + "name": "Usenet", + "description": "Ti consente di vedere la coda e la cronologia di usenet (Sabnzbd o NZBGet), mettere in pausa e riprendere i download." }, "card": { "errors": { diff --git a/public/locales/ja/modules/usenet.json b/public/locales/ja/modules/usenet.json index 7d8467731..7f5c81b93 100644 --- a/public/locales/ja/modules/usenet.json +++ b/public/locales/ja/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Sabnzbdのキューと履歴の確認、ダウンロードの一時停止と再開が可能です。" + "name": "ユースネット", + "description": "Usenet (Sabnzbd または NZBGet) のキューと履歴を表示し、ダウンロードを一時停止および再開できます。" }, "card": { "errors": { diff --git a/public/locales/ko/modules/usenet.json b/public/locales/ko/modules/usenet.json index 96e6d6251..d5ca9b14d 100644 --- a/public/locales/ko/modules/usenet.json +++ b/public/locales/ko/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Sabnzbd의 대기열 및 기록을 확인하고 다운로드를 일시 중지 및 재개할 수 있습니다" + "name": "ユースネット", + "description": "유즈넷(Sabnzbd 또는 NZBGet) 대기열 및 기록을 보고 다운로드를 일시 중지 및 재개할 수 있습니다." }, "card": { "errors": { diff --git a/public/locales/lol/modules/usenet.json b/public/locales/lol/modules/usenet.json index 9139a27e6..cd126d151 100644 --- a/public/locales/lol/modules/usenet.json +++ b/public/locales/lol/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Allowz u 2 c ur Sabnzbd queue an histowee, paues an resuem downloadz" + "name": "Usenet", + "description": "Allowz u 2 c ur usenet (Sabnzbd & NZBGet) queue an histowee, paues an resuem downloadz" }, "card": { "errors": { diff --git a/public/locales/nl/modules/usenet.json b/public/locales/nl/modules/usenet.json index ec91a50c6..e9a981b14 100644 --- a/public/locales/nl/modules/usenet.json +++ b/public/locales/nl/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Hiermee kunt u uw Sabnzbd wachtrij en geschiedenis weergeven, downloads pauzeren en hervatten" + "name": "Usenet", + "description": "Hiermee kunt u uw usenet (Sabnzbd of NZBGet) wachtrij en geschiedenis bekijken, downloads pauzeren en hervatten." }, "card": { "errors": { diff --git a/public/locales/pl/modules/usenet.json b/public/locales/pl/modules/usenet.json index 5d9224cc7..67cdd5eef 100644 --- a/public/locales/pl/modules/usenet.json +++ b/public/locales/pl/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Pozwala zobaczyć kolejkę i historię Sabnzbd, wstrzymywać i wznawiać pobieranie" + "name": "Usenet", + "description": "Pozwala zobaczyć kolejkę i historię usenetu (Sabnzbd lub NZBGet), wstrzymywać i wznawiać pobieranie." }, "card": { "errors": { diff --git a/public/locales/pt/modules/usenet.json b/public/locales/pt/modules/usenet.json index c975cb4e0..e55578f80 100644 --- a/public/locales/pt/modules/usenet.json +++ b/public/locales/pt/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "", - "description": "" + "name": "Usenet", + "description": "Permite que você veja sua fila e histórico de usenet (Sabnzbd ou NZBGet), pausar e retomar downloads." }, "card": { "errors": { diff --git a/public/locales/ru/modules/usenet.json b/public/locales/ru/modules/usenet.json index b223d452d..f0a923346 100644 --- a/public/locales/ru/modules/usenet.json +++ b/public/locales/ru/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "", - "description": "" + "name": "Юнет", + "description": "Позволяет просматривать очередь и историю Usenet (Sabnzbd или NZBGet), приостанавливать и возобновлять загрузки." }, "card": { "errors": { diff --git a/public/locales/sl/modules/usenet.json b/public/locales/sl/modules/usenet.json index dc7c76ff0..1bae3d448 100644 --- a/public/locales/sl/modules/usenet.json +++ b/public/locales/sl/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Omogoča ogled čakalne vrste in zgodovine v Sabnzbd ter prekinitev in nadaljevanje prenosov" + "name": "Usenet", + "description": "Omogoča ogled vaše čakalne vrste in zgodovine usenet (Sabnzbd ali NZBGet), zaustavitev in nadaljevanje prenosov." }, "card": { "errors": { diff --git a/public/locales/sv/modules/usenet.json b/public/locales/sv/modules/usenet.json index c03ee1e6a..776c42fde 100644 --- a/public/locales/sv/modules/usenet.json +++ b/public/locales/sv/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "Låter dig se din Sabnzbd kö och historik, pausa och återuppta nedladdningar" + "name": "Usenet", + "description": "Låter dig se din usenet (Sabnzbd eller NZBGet) kö och historik, pausa och återuppta nedladdningar." }, "card": { "errors": { diff --git a/public/locales/uk/modules/usenet.json b/public/locales/uk/modules/usenet.json index 6bd901f0c..4cf378f3a 100644 --- a/public/locales/uk/modules/usenet.json +++ b/public/locales/uk/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { "name": "Sabnzbd", - "description": "Дозволяє вам бачити вашу чергу в Sabnzbd і історію, призупинення і відновлення завантажень" + "description": "Дозволяє переглядати чергу та історію usenet (Sabnzbd або NZBGet), призупиняти та відновлювати завантаження." }, "card": { "errors": { diff --git a/public/locales/zh/modules/usenet.json b/public/locales/zh/modules/usenet.json index 901d18f94..d59137333 100644 --- a/public/locales/zh/modules/usenet.json +++ b/public/locales/zh/modules/usenet.json @@ -1,7 +1,7 @@ { "descriptor": { - "name": "Sabnzbd", - "description": "允许您查看您的Sabnzbd队列和历史,进行暂停和恢复下载操作" + "name": "用户网", + "description": "允许您查看您的用户网(Sabnzbd 或 NZBGet)队列和历史记录,暂停和恢复下载。" }, "card": { "errors": { diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index e447f1dc0..80f7642b6 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -407,6 +407,42 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) { /> )} + {form.values.type === 'NZBGet' && ( + <> + { + form.setFieldValue('username', event.currentTarget.value); + }} + error={ + form.errors.username && + t( + 'modal.tabs.options.form.integrations.nzbget.username.validation.invalidUsername' + ) + } + /> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={ + form.errors.password && + t( + 'modal.tabs.options.form.integrations.nzbget.password.validation.invalidPassword' + ) + } + /> + + )} diff --git a/src/modules/usenet/UsenetModule.tsx b/src/modules/usenet/UsenetModule.tsx index f03084e69..081be509f 100644 --- a/src/modules/usenet/UsenetModule.tsx +++ b/src/modules/usenet/UsenetModule.tsx @@ -16,7 +16,7 @@ import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; dayjs.extend(duration); export const UsenetComponent: FunctionComponent = () => { - const downloadServices = useGetServiceByType('Sabnzbd'); + const downloadServices = useGetServiceByType('Sabnzbd', 'NZBGet'); const { t } = useTranslation('modules/usenet'); diff --git a/src/pages/api/modules/usenet/history.ts b/src/pages/api/modules/usenet/history.ts index 4143428f4..67f4ececd 100644 --- a/src/pages/api/modules/usenet/history.ts +++ b/src/pages/api/modules/usenet/history.ts @@ -7,6 +7,8 @@ import { UsenetHistoryItem } from '../../../../modules'; import { getConfig } from '../../../../tools/getConfig'; import { getServiceById } from '../../../../tools/hooks/useGetServiceByType'; import { Config } from '../../../../tools/types'; +import { NzbgetHistoryItem } from './nzbget/types'; +import { NzbgetClient } from './nzbget/nzbget-client'; dayjs.extend(duration); @@ -33,23 +35,72 @@ async function Get(req: NextApiRequest, res: NextApiResponse) { throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`); } - if (!service.apiKey) { - throw new Error(`API Key for service "${service.name}" is missing`); + let response: UsenetHistoryResponse; + switch (service.type) { + case 'NZBGet': { + const url = new URL(service.url); + const options = { + host: url.hostname, + port: url.port, + login: service.username, + hash: service.password, + }; + + const nzbGet = NzbgetClient(options); + + const nzbgetHistory:NzbgetHistoryItem[] = await new Promise((resolve, reject) => { + nzbGet.history(false, (err: any, result: NzbgetHistoryItem[]) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + + if (!nzbgetHistory) { + throw new Error('Error while getting NZBGet history'); + } + + const nzbgetItems: UsenetHistoryItem[] = nzbgetHistory.map((item: NzbgetHistoryItem) => ({ + id: item.NZBID.toString(), + name: item.Name, + // Convert from MB to bytes + size: item.DownloadedSizeMB * 1000000, + time: item.DownloadTimeSec, + })); + + response = { + items: nzbgetItems, + total: nzbgetItems.length, + }; + break; + } + case 'Sabnzbd': { + const { origin } = new URL(service.url); + + if (!service.apiKey) { + throw new Error(`API Key for service "${service.name}" is missing`); + } + + const history = await new Client(origin, service.apiKey).history(offset, limit); + + const items: UsenetHistoryItem[] = history.slots.map((slot) => ({ + id: slot.nzo_id, + name: slot.name, + size: slot.bytes, + time: slot.download_time, + })); + + response = { + items, + total: history.noofslots, + }; + break; + } + default: + throw new Error(`Service type "${service.type}" unrecognized.`); } - const { origin } = new URL(service.url); - - const history = await new Client(origin, service.apiKey).history(offset, limit); - - const items: UsenetHistoryItem[] = history.slots.map((slot) => ({ - id: slot.nzo_id, - name: slot.name, - size: slot.bytes, - time: slot.download_time, - })); - const response: UsenetHistoryResponse = { - items, - total: history.noofslots, - }; return res.status(200).json(response); } catch (err) { diff --git a/src/pages/api/modules/usenet/index.ts b/src/pages/api/modules/usenet/index.ts index f40d2c11a..bf0cffc1a 100644 --- a/src/pages/api/modules/usenet/index.ts +++ b/src/pages/api/modules/usenet/index.ts @@ -6,6 +6,8 @@ import { Client } from 'sabnzbd-api'; import { getConfig } from '../../../../tools/getConfig'; import { getServiceById } from '../../../../tools/hooks/useGetServiceByType'; import { Config } from '../../../../tools/types'; +import { NzbgetStatus } from './nzbget/types'; +import { NzbgetClient } from './nzbget/nzbget-client'; dayjs.extend(duration); @@ -32,28 +34,71 @@ async function Get(req: NextApiRequest, res: NextApiResponse) { throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`); } - if (!service.apiKey) { - throw new Error(`API Key for service "${service.name}" is missing`); + let response: UsenetInfoResponse; + switch (service.type) { + case 'NZBGet': { + const url = new URL(service.url); + const options = { + host: url.hostname, + port: url.port, + login: service.username, + hash: service.password, + }; + + const nzbGet = NzbgetClient(options); + + const nzbgetStatus:NzbgetStatus = await new Promise((resolve, reject) => { + nzbGet.status((err: any, result: NzbgetStatus) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + + if (!nzbgetStatus) { + throw new Error('Error while getting NZBGet status'); + } + + const bytesRemaining = nzbgetStatus.RemainingSizeMB * 1000000; + const eta = bytesRemaining / nzbgetStatus.DownloadRate; + response = { + paused: nzbgetStatus.DownloadPaused, + sizeLeft: bytesRemaining, + speed: nzbgetStatus.DownloadRate, + eta, + }; + break; + } + case 'Sabnzbd': { + if (!service.apiKey) { + throw new Error(`API Key for service "${service.name}" is missing`); + } + + const { origin } = new URL(service.url); + + const queue = await new Client(origin, service.apiKey).queue(0, -1); + + const [hours, minutes, seconds] = queue.timeleft.split(':'); + const eta = dayjs.duration({ + hour: parseInt(hours, 10), + minutes: parseInt(minutes, 10), + seconds: parseInt(seconds, 10), + } as any); + + response = { + paused: queue.paused, + sizeLeft: parseFloat(queue.mbleft) * 1024 * 1024, + speed: parseFloat(queue.kbpersec) * 1000, + eta: eta.asSeconds(), + }; + break; + } + default: + throw new Error(`Service type "${service.type}" unrecognized.`); } - const { origin } = new URL(service.url); - - const queue = await new Client(origin, service.apiKey).queue(0, -1); - - const [hours, minutes, seconds] = queue.timeleft.split(':'); - const eta = dayjs.duration({ - hour: parseInt(hours, 10), - minutes: parseInt(minutes, 10), - seconds: parseInt(seconds, 10), - } as any); - - const response: UsenetInfoResponse = { - paused: queue.paused, - sizeLeft: parseFloat(queue.mbleft) * 1024 * 1024, - speed: parseFloat(queue.kbpersec) * 1000, - eta: eta.asSeconds(), - }; - return res.status(200).json(response); } catch (err) { return res.status(500).send((err as any).message); diff --git a/src/pages/api/modules/usenet/nzbget/nzbget-api.d.ts b/src/pages/api/modules/usenet/nzbget/nzbget-api.d.ts new file mode 100644 index 000000000..9c77b63dc --- /dev/null +++ b/src/pages/api/modules/usenet/nzbget/nzbget-api.d.ts @@ -0,0 +1 @@ +declare module 'nzbget-api'; diff --git a/src/pages/api/modules/usenet/nzbget/nzbget-client.ts b/src/pages/api/modules/usenet/nzbget/nzbget-client.ts new file mode 100644 index 000000000..80fe82295 --- /dev/null +++ b/src/pages/api/modules/usenet/nzbget/nzbget-client.ts @@ -0,0 +1,22 @@ +import NZBGet from 'nzbget-api'; +import { NzbgetClientOptions } from './types'; + +export function NzbgetClient(options: NzbgetClientOptions) { + if (!options?.host) { + throw new Error('Cannot connect to NZBGet. Missing host in service config.'); + } + + if (!options?.port) { + throw new Error('Cannot connect to NZBGet. Missing port in service config.'); + } + + if (!options?.login) { + throw new Error('Cannot connect to NZBGet. Missing username in service config.'); + } + + if (!options?.hash) { + throw new Error('Cannot connect to NZBGet. Missing password in service config.'); + } + + return new NZBGet(options); +} diff --git a/src/pages/api/modules/usenet/nzbget/types.ts b/src/pages/api/modules/usenet/nzbget/types.ts new file mode 100644 index 000000000..d2bc53ad7 --- /dev/null +++ b/src/pages/api/modules/usenet/nzbget/types.ts @@ -0,0 +1,135 @@ +export interface NzbgetHistoryItem { + NZBID: number; + Kind: 'NZB' | 'URL' | 'DUP'; + NZBFilename: string; + Name: string; + URL: string; + HistoryTime: number; + DestDir: string; + FinalDir: string; + Category: string; + FileSizeLo: number; + FileSizeHi: number; + FileSizeMB: number; + FileCount: number; + RemainingFileCount: number; + MinPostTime: number; + MaxPostTime: number; + TotalArticles: number; + SuccessArticles: number; + FailedArticles: number; + Health: number; + DownloadedSizeLo: number; + DownloadedSizeHi: number; + DownloadedSizeMB: number; + DownloadTimeSec: number; + PostTotalTimeSec: number; + ParTimeSec: number; + RepairTimeSec: number + UnpackTimeSec: number; + MessageCount:number; + DupeKey: string; + DupeScore: number; + DupeMode: 'SCORE' | 'ALL' | 'FORCE'; + Status: string; + ParStatus: 'NONE' | 'FAILURE' | 'REPAIR_POSSIBLE' | 'SUCCESS' | 'MANUAL'; + ExParStatus: 'RECIPIENT' | 'DONOR'; + UnpackStatus: 'NONE' | 'FAILURE' | 'SPACE' | 'PASSWORD' | 'SUCCESS'; + UrlStatus: 'NONE' | 'SUCCESS' | 'FAILURE' | 'SCAN_SKIPPED' | 'SCAN_FAILURE'; + ScriptStatus: 'NONE' | 'FAILURE' | 'SUCCESS'; + ScriptStatuses: []; + MoveStatus: 'NONE' | 'SUCCESS' | 'FAILURE'; + DeleteStatus: 'NONE' | 'MANUAL' | 'HEALTH' | 'DUPE' | 'BAD' | 'SCAN' | 'COPY'; + MarkStatus: 'NONE' | 'GOOD' | 'BAD'; + ExtraParBlocks: number; + Parameters: []; + ServerStats: []; +} + +export interface NzbgetQueueItem { + NZBID: number; + NZBFilename: string; + NZBName: string; + Kind: 'NZB' | 'URL'; + URL: string; + DestDir: string; + FinalDir: string; + Category: string; + FileSizeLo: number; + FileSizeHi: number; + FileSizeMB: number; + RemainingSizeLo: number; + RemainingSizeHi: number; + RemainingSizeMB: number; + PausedSizeLo: number; + PausedSizeHi: number; + PausedSizeMB: number; + FileCount: number; + RemainingFileCount: number; + RemainingParCount: number; + MinPostTime: number; + MaxPostTime: number; + MaxPriority: number; + ActiveDownloads: number; + Status: 'QUEUED' | 'PAUSED' | 'DOWNLOADING' | 'FETCHING' | 'PP_QUEUED' | 'LOADING_PARS' | 'VERIFYING_SOURCES' | 'REPAIRING' | 'VERIFYING_REPAIRED' | 'RENAMING' | 'UNPACKING' | 'MOVING' | 'EXECUTING_SCRIPT' | 'PP_FINISHED'; + TotalArticles: number; + SuccessArticles: number; + FailedArticles: number; + Health: number; + CriticalHealth: number; + DownloadedSizeLo: number; + DownloadedSizeHi: number; + DownloadedSizeMB: number; + DownloadTimeSec: number; + MessageCount: number; + DupeKey: string; + DupeScore: number; + DupeMode: string; + Parameters: []; + ServerStats: []; + PostInfoText: string; + PostStageProgress: number; + PostTotalTimeSec: number; + PostStageTimeSec: number; +} + +export interface NzbgetStatus { + RemainingSizeLo: number; + RemainingSizeHi: number; + RemainingSizeMB: number; + ForcedSizeLo: number; + ForcedSizeHi: number; + ForcedSizeMB: number; + DownloadedSizeLo: number; + DownloadedSizeHi: number; + DownloadedSizeMB: number; + ArticleCacheLo: number; + ArticleCacheHi: number; + ArticleCacheMB: number; + DownloadRate: number; + AverageDownloadRate: number; + DownloadLimit: number; + ThreadCount: number; + PostJobCount: number; + UrlCount: number; + UpTimeSec: number; + DownloadTimeSec: number; + ServerStandBy: boolean; + DownloadPaused: boolean; + PostPaused: boolean; + ScanPaused: boolean; + ServerTime: number; + ResumeTime: number; + FeedActive: boolean; + FreeDiskSpaceLo: number; + FreeDiskSpaceHi: number; + FreeDiskSpaceMB: number; + NewsServers: []; +} + +export interface NzbgetClientOptions { + host: string, + port: string, + login: string | undefined, + hash: string | undefined, +} diff --git a/src/pages/api/modules/usenet/pause.ts b/src/pages/api/modules/usenet/pause.ts index 53270d6be..9ce84015d 100644 --- a/src/pages/api/modules/usenet/pause.ts +++ b/src/pages/api/modules/usenet/pause.ts @@ -6,6 +6,7 @@ import { Client } from 'sabnzbd-api'; import { getConfig } from '../../../../tools/getConfig'; import { getServiceById } from '../../../../tools/hooks/useGetServiceByType'; import { Config } from '../../../../tools/types'; +import { NzbgetClient } from './nzbget/nzbget-client'; dayjs.extend(duration); @@ -25,14 +26,44 @@ async function Post(req: NextApiRequest, res: NextApiResponse) { throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`); } - if (!service.apiKey) { - throw new Error(`API Key for service "${service.name}" is missing`); + let result; + switch (service.type) { + case 'NZBGet': { + const url = new URL(service.url); + const options = { + host: url.hostname, + port: url.port, + login: service.username, + hash: service.password, + }; + + const nzbGet = NzbgetClient(options); + + result = await new Promise((resolve, reject) => { + nzbGet.pauseDownload(false, (err: any, result: any) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + break; + } + case 'Sabnzbd': { + if (!service.apiKey) { + throw new Error(`API Key for service "${service.name}" is missing`); + } + + const { origin } = new URL(service.url); + + result = await new Client(origin, service.apiKey).queuePause(); + break; + } + default: + throw new Error(`Service type "${service.type}" unrecognized.`); } - const { origin } = new URL(service.url); - - const result = await new Client(origin, service.apiKey).queuePause(); - return res.status(200).json(result); } catch (err) { return res.status(500).send((err as any).message); diff --git a/src/pages/api/modules/usenet/queue.ts b/src/pages/api/modules/usenet/queue.ts index d1fc07d1d..5ae73436e 100644 --- a/src/pages/api/modules/usenet/queue.ts +++ b/src/pages/api/modules/usenet/queue.ts @@ -7,6 +7,8 @@ import { UsenetQueueItem } from '../../../../modules'; import { getConfig } from '../../../../tools/getConfig'; import { getServiceById } from '../../../../tools/hooks/useGetServiceByType'; import { Config } from '../../../../tools/types'; +import { NzbgetClient } from './nzbget/nzbget-client'; +import { NzbgetQueueItem, NzbgetStatus } from './nzbget/types'; dayjs.extend(duration); @@ -33,42 +35,116 @@ async function Get(req: NextApiRequest, res: NextApiResponse) { throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`); } - if (!service.apiKey) { - throw new Error(`API Key for service "${service.name}" is missing`); + let response: UsenetQueueResponse; + switch (service.type) { + case 'NZBGet': { + const url = new URL(service.url); + const options = { + host: url.hostname, + port: url.port, + login: service.username, + hash: service.password, + }; + + const nzbGet = NzbgetClient(options); + + const nzbgetQueue:NzbgetQueueItem[] = await new Promise((resolve, reject) => { + nzbGet.listGroups((err: any, result: NzbgetQueueItem[]) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + + if (!nzbgetQueue) { + throw new Error('Error while getting NZBGet queue'); + } + + const nzbgetStatus:NzbgetStatus = await new Promise((resolve, reject) => { + nzbGet.status((err: any, result: NzbgetStatus) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + + if (!nzbgetStatus) { + throw new Error('Error while getting NZBGet status'); + } + + const nzbgetItems: UsenetQueueItem[] = nzbgetQueue.map((item: NzbgetQueueItem) => ({ + id: item.NZBID.toString(), + name: item.NZBName, + progress: (item.DownloadedSizeMB / item.FileSizeMB) * 100, + eta: (item.RemainingSizeMB * 1000000) / nzbgetStatus.DownloadRate, + // Multiple MB to get bytes + size: item.FileSizeMB * 1000 * 1000, + state: getNzbgetState(item.Status), + })); + + response = { + items: nzbgetItems, + total: nzbgetItems.length, + }; + break; + } + case 'Sabnzbd': { + if (!service.apiKey) { + throw new Error(`API Key for service "${service.name}" is missing`); + } + + const { origin } = new URL(service.url); + const queue = await new Client(origin, service.apiKey).queue(offset, limit); + + const items: UsenetQueueItem[] = queue.slots.map((slot) => { + const [hours, minutes, seconds] = slot.timeleft.split(':'); + const eta = dayjs.duration({ + hour: parseInt(hours, 10), + minutes: parseInt(minutes, 10), + seconds: parseInt(seconds, 10), + } as any); + + return { + id: slot.nzo_id, + eta: eta.asSeconds(), + name: slot.filename, + progress: parseFloat(slot.percentage), + size: parseFloat(slot.mb) * 1000 * 1000, + state: slot.status.toLowerCase() as any, + }; + }); + + response = { + items, + total: queue.noofslots, + }; + break; + } + default: + throw new Error(`Service type "${service.type}" unrecognized.`); } - const { origin } = new URL(service.url); - const queue = await new Client(origin, service.apiKey).queue(offset, limit); - - const items: UsenetQueueItem[] = queue.slots.map((slot) => { - const [hours, minutes, seconds] = slot.timeleft.split(':'); - const eta = dayjs.duration({ - hour: parseInt(hours, 10), - minutes: parseInt(minutes, 10), - seconds: parseInt(seconds, 10), - } as any); - - return { - id: slot.nzo_id, - eta: eta.asSeconds(), - name: slot.filename, - progress: parseFloat(slot.percentage), - size: parseFloat(slot.mb) * 1000 * 1000, - state: slot.status.toLowerCase() as any, - }; - }); - - const response: UsenetQueueResponse = { - items, - total: queue.noofslots, - }; - return res.status(200).json(response); } catch (err) { return res.status(500).send((err as any).message); } } +function getNzbgetState(status: string) { + switch (status) { + case 'QUEUED': + return 'queued'; + case 'PAUSED ': + return 'paused'; + default: + return 'downloading'; + } +} + export default async (req: NextApiRequest, res: NextApiResponse) => { // Filter out if the reuqest is a POST or a GET if (req.method === 'GET') { diff --git a/src/pages/api/modules/usenet/resume.ts b/src/pages/api/modules/usenet/resume.ts index f3b155f06..444f00d34 100644 --- a/src/pages/api/modules/usenet/resume.ts +++ b/src/pages/api/modules/usenet/resume.ts @@ -6,6 +6,7 @@ import { Client } from 'sabnzbd-api'; import { getConfig } from '../../../../tools/getConfig'; import { getServiceById } from '../../../../tools/hooks/useGetServiceByType'; import { Config } from '../../../../tools/types'; +import { NzbgetClient } from './nzbget/nzbget-client'; dayjs.extend(duration); @@ -26,14 +27,44 @@ async function Post(req: NextApiRequest, res: NextApiResponse) { throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`); } - if (!service.apiKey) { - throw new Error(`API Key for service "${service.name}" is missing`); + let result; + switch (service.type) { + case 'NZBGet': { + const url = new URL(service.url); + const options = { + host: url.hostname, + port: url.port, + login: service.username, + hash: service.password, + }; + + const nzbGet = NzbgetClient(options); + + result = await new Promise((resolve, reject) => { + nzbGet.resumeDownload(false, (err: any, result: any) => { + if (!err) { + resolve(result); + } else { + reject(err); + } + }); + }); + break; + } + case 'Sabnzbd': { + if (!service.apiKey) { + throw new Error(`API Key for service "${service.name}" is missing`); + } + + const { origin } = new URL(service.url); + + result = await new Client(origin, service.apiKey).queueResume(); + break; + } + default: + throw new Error(`Service type "${service.type}" unrecognized.`); } - const { origin } = new URL(service.url); - - const result = await new Client(origin, service.apiKey).queueResume(); - return res.status(200).json(result); } catch (err) { return res.status(500).send((err as any).message); diff --git a/src/tools/types.ts b/src/tools/types.ts index da2116a6e..7c5ac923b 100644 --- a/src/tools/types.ts +++ b/src/tools/types.ts @@ -76,6 +76,7 @@ export const ServiceTypeList = [ 'Overseerr', 'Jellyseerr', 'Sabnzbd', + 'NZBGet' ]; export type ServiceType = | 'Other' @@ -91,7 +92,8 @@ export type ServiceType = | 'Overseerr' | 'Jellyseerr' | 'Transmission' - | 'Sabnzbd'; + | 'Sabnzbd' + | 'NZBGet'; export function tryMatchPort(name: string | undefined, form?: any) { if (!name) { @@ -118,6 +120,7 @@ export const portmap = [ { name: 'overseerr', value: '5055' }, { name: 'dash.', value: '3001' }, { name: 'sabnzbd', value: '8080' }, + { name: 'nzbget', value: '6789' } ]; export const MatchingImages: {