mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
🏗️ Migrate usenet info to tRPC
This commit is contained in:
@@ -13,25 +13,27 @@ import { UsenetInfoRequestParams, UsenetInfoResponse } from '../../../pages/api/
|
|||||||
import { UsenetPauseRequestParams } from '../../../pages/api/modules/usenet/pause';
|
import { UsenetPauseRequestParams } from '../../../pages/api/modules/usenet/pause';
|
||||||
import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool';
|
import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool';
|
||||||
import { UsenetResumeRequestParams } from '../../../pages/api/modules/usenet/resume';
|
import { UsenetResumeRequestParams } from '../../../pages/api/modules/usenet/resume';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
|
import { useConfigContext } from '~/config/provider';
|
||||||
|
|
||||||
const POLLING_INTERVAL = 2000;
|
const POLLING_INTERVAL = 2000;
|
||||||
|
|
||||||
export const useGetUsenetInfo = (params: UsenetInfoRequestParams) =>
|
export const useGetUsenetInfo = ({ appId }: UsenetInfoRequestParams) => {
|
||||||
useQuery(
|
const { name: configName } = useConfigContext();
|
||||||
['usenetInfo', params.appId],
|
|
||||||
async () =>
|
return api.usenet.info.useQuery(
|
||||||
(
|
{
|
||||||
await axios.get<UsenetInfoResponse>('/api/modules/usenet', {
|
appId,
|
||||||
params,
|
configName: configName!,
|
||||||
})
|
},
|
||||||
).data,
|
|
||||||
{
|
{
|
||||||
refetchInterval: POLLING_INTERVAL,
|
refetchInterval: POLLING_INTERVAL,
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
retry: 2,
|
retry: 2,
|
||||||
enabled: Boolean(params.appId),
|
enabled: !!appId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetUsenetDownloads = (params: UsenetQueueRequestParams) =>
|
export const useGetUsenetDownloads = (params: UsenetQueueRequestParams) =>
|
||||||
useQuery(
|
useQuery(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { downloadRouter } from './routers/download';
|
|||||||
import { mediaRequestsRouter } from './routers/media-request';
|
import { mediaRequestsRouter } from './routers/media-request';
|
||||||
import { mediaServerRouter } from './routers/media-server';
|
import { mediaServerRouter } from './routers/media-server';
|
||||||
import { overseerrRouter } from './routers/overseerr';
|
import { overseerrRouter } from './routers/overseerr';
|
||||||
|
import { usenetRouter } from './routers/usenet/route';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -28,6 +29,7 @@ export const rootRouter = createTRPCRouter({
|
|||||||
mediaRequest: mediaRequestsRouter,
|
mediaRequest: mediaRequestsRouter,
|
||||||
mediaServer: mediaServerRouter,
|
mediaServer: mediaServerRouter,
|
||||||
overseerr: overseerrRouter,
|
overseerr: overseerrRouter,
|
||||||
|
usenet: usenetRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import Consola from 'consola';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Client } from 'sabnzbd-api';
|
import { Client } from 'sabnzbd-api';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { NzbgetClient } from '~/pages/api/modules/usenet/nzbget/nzbget-client';
|
import { NzbgetClient } from '~/server/api/routers/usenet/nzbget/nzbget-client';
|
||||||
import { NzbgetQueueItem, NzbgetStatus } from '~/pages/api/modules/usenet/nzbget/types';
|
import { NzbgetQueueItem, NzbgetStatus } from '~/server/api/routers/usenet/nzbget/types';
|
||||||
import { getConfig } from '~/tools/config/getConfig';
|
import { getConfig } from '~/tools/config/getConfig';
|
||||||
import {
|
import {
|
||||||
NormalizedDownloadAppStat,
|
NormalizedDownloadAppStat,
|
||||||
|
|||||||
1
src/server/api/routers/usenet/nzbget/nzbget-api.d.ts
vendored
Normal file
1
src/server/api/routers/usenet/nzbget/nzbget-api.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module 'nzbget-api';
|
||||||
22
src/server/api/routers/usenet/nzbget/nzbget-client.ts
Normal file
22
src/server/api/routers/usenet/nzbget/nzbget-client.ts
Normal file
@@ -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 app config.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options?.port) {
|
||||||
|
throw new Error('Cannot connect to NZBGet. Missing port in app config.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options?.login) {
|
||||||
|
throw new Error('Cannot connect to NZBGet. Missing username in app config.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options?.hash) {
|
||||||
|
throw new Error('Cannot connect to NZBGet. Missing password in app config.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NZBGet(options);
|
||||||
|
}
|
||||||
149
src/server/api/routers/usenet/nzbget/types.ts
Normal file
149
src/server/api/routers/usenet/nzbget/types.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
101
src/server/api/routers/usenet/route.ts
Normal file
101
src/server/api/routers/usenet/route.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { Client } from 'sabnzbd-api';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { TRPCError } from '@trpc/server';
|
||||||
|
import { NzbgetStatus } from '~/server/api/routers/usenet/nzbget/types';
|
||||||
|
import { getConfig } from '~/tools/config/getConfig';
|
||||||
|
import { createTRPCRouter, publicProcedure } from '../../trpc';
|
||||||
|
import { NzbgetClient } from './nzbget/nzbget-client';
|
||||||
|
|
||||||
|
export const usenetRouter = createTRPCRouter({
|
||||||
|
info: publicProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
configName: z.string(),
|
||||||
|
appId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const config = getConfig(input.configName);
|
||||||
|
|
||||||
|
const app = config.apps.find((x) => x.id === input.appId);
|
||||||
|
|
||||||
|
if (!app || (app.integration?.type !== 'nzbGet' && app.integration?.type !== 'sabnzbd')) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: `App with ID "${input.appId}" could not be found.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.integration?.type === 'nzbGet') {
|
||||||
|
const url = new URL(app.url);
|
||||||
|
const options = {
|
||||||
|
host: url.hostname,
|
||||||
|
port: url.port || (url.protocol === 'https:' ? '443' : '80'),
|
||||||
|
login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
||||||
|
hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'Error while getting NZBGet status',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytesRemaining = nzbgetStatus.RemainingSizeMB * 1000000;
|
||||||
|
const eta = bytesRemaining / nzbgetStatus.DownloadRate;
|
||||||
|
return {
|
||||||
|
paused: nzbgetStatus.DownloadPaused,
|
||||||
|
sizeLeft: bytesRemaining,
|
||||||
|
speed: nzbgetStatus.DownloadRate,
|
||||||
|
eta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: `API Key for app "${app.name}" is missing`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { origin } = new URL(app.url);
|
||||||
|
|
||||||
|
const queue = await new Client(origin, 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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
paused: queue.paused,
|
||||||
|
sizeLeft: parseFloat(queue.mbleft) * 1024 * 1024,
|
||||||
|
speed: parseFloat(queue.kbpersec) * 1000,
|
||||||
|
eta: eta.asSeconds(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface UsenetInfoResponse {
|
||||||
|
paused: boolean;
|
||||||
|
sizeLeft: number;
|
||||||
|
speed: number;
|
||||||
|
eta: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user