Merge branch 'gridstack' of https://github.com/manuel-rw/homarr into gridstack

This commit is contained in:
Meierschlumpf
2022-12-18 22:59:29 +01:00
69 changed files with 661 additions and 495 deletions

View File

@@ -1,7 +1,7 @@
import axios from 'axios';
import { NextApiRequest, NextApiResponse } from 'next';
import { getConfig } from '../../../tools/config/getConfig';
import { ServiceIntegrationType } from '../../../types/service';
import { AppIntegrationType } from '../../../types/app';
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a POST or a GET
@@ -15,7 +15,7 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
};
async function Get(req: NextApiRequest, res: NextApiResponse) {
// Parse req.body as a ServiceItem
// Parse req.body as a AppItem
const {
month: monthString,
year: yearString,
@@ -34,20 +34,20 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
const config = getConfig(configName);
const mediaServiceIntegrationTypes: ServiceIntegrationType['type'][] = [
const mediaAppIntegrationTypes: AppIntegrationType['type'][] = [
'sonarr',
'radarr',
'readarr',
'lidarr',
];
const mediaServices = config.services.filter(
(service) =>
service.integration && mediaServiceIntegrationTypes.includes(service.integration.type)
const mediaApps = config.apps.filter(
(app) =>
app.integration && mediaAppIntegrationTypes.includes(app.integration.type)
);
const medias = await Promise.all(
await mediaServices.map(async (service) => {
const integration = service.integration!;
await mediaApps.map(async (app) => {
const integration = app.integration!;
const endpoint = IntegrationTypeEndpointMap.get(integration.type);
if (!endpoint) {
return {
@@ -57,7 +57,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
}
// Get the origin URL
let { href: origin } = new URL(service.url);
let { href: origin } = new URL(app.url);
if (origin.endsWith('/')) {
origin = origin.slice(0, -1);
}
@@ -85,7 +85,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
});
}
const IntegrationTypeEndpointMap = new Map<ServiceIntegrationType['type'], string>([
const IntegrationTypeEndpointMap = new Map<AppIntegrationType['type'], string>([
['sonarr', '/api/calendar'],
['radarr', '/api/v3/calendar'],
['lidarr', '/api/v1/calendar'],

View File

@@ -11,8 +11,8 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
const { id, type } = req.query as { id: string; type: string };
const configName = getCookie('config-name', { req });
const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
const service = config.services.find(
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
const app = config.apps.find(
(app) => app.type === 'Overseerr' || app.type === 'Jellyseerr'
);
if (!id) {
return res.status(400).json({ error: 'No id provided' });
@@ -20,18 +20,18 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
if (!type) {
return res.status(400).json({ error: 'No type provided' });
}
if (!service?.apiKey) {
return res.status(400).json({ error: 'No service found' });
if (!app?.apiKey) {
return res.status(400).json({ error: 'No apps found' });
}
const serviceUrl = new URL(service.url);
const appUrl = new URL(app.url);
switch (type) {
case 'movie':
return axios
.get(`${serviceUrl.origin}/api/v1/movie/${id}`, {
.get(`${appUrl.origin}/api/v1/movie/${id}`, {
headers: {
// Set X-Api-Key to the value of the API key
'X-Api-Key': service.apiKey,
'X-Api-Key': app.apiKey,
},
})
.then((axiosres) => res.status(200).json(axiosres.data))
@@ -45,10 +45,10 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
case 'tv':
// Make request to the tv api
return axios
.get(`${serviceUrl.origin}/api/v1/tv/${id}`, {
.get(`${appUrl.origin}/api/v1/tv/${id}`, {
headers: {
// Set X-Api-Key to the value of the API key
'X-Api-Key': service.apiKey,
'X-Api-Key': app.apiKey,
},
})
.then((axiosres) => res.status(200).json(axiosres.data))
@@ -72,8 +72,8 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
const { seasons, type } = req.body as { seasons?: number[]; type: MediaType };
const configName = getCookie('config-name', { req });
const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
const service = config.services.find(
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
const app = config.apps.find(
(app) => app.type === 'Overseerr' || app.type === 'Jellyseerr'
);
if (!id) {
return res.status(400).json({ error: 'No id provided' });
@@ -81,13 +81,13 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
if (!type) {
return res.status(400).json({ error: 'No type provided' });
}
if (!service?.apiKey) {
return res.status(400).json({ error: 'No service found' });
if (!app?.apiKey) {
return res.status(400).json({ error: 'No app found' });
}
if (type === 'movie' && !seasons) {
return res.status(400).json({ error: 'No seasons provided' });
}
const serviceUrl = new URL(service.url);
const appUrl = new URL(app.url);
Consola.info('Got an Overseerr request with these arguments', {
mediaType: type,
mediaId: id,
@@ -95,7 +95,7 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
});
return axios
.post(
`${serviceUrl.origin}/api/v1/request`,
`${appUrl.origin}/api/v1/request`,
{
mediaType: type,
mediaId: Number(id),
@@ -104,7 +104,7 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
{
headers: {
// Set X-Api-Key to the value of the API key
'X-Api-Key': service.apiKey,
'X-Api-Key': app.apiKey,
},
}
)

View File

@@ -8,24 +8,24 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
const configName = getCookie('config-name', { req });
const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
const { query } = req.query;
const service = config.services.find(
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
const app = config.apps.find(
(app) => app.type === 'Overseerr' || app.type === 'Jellyseerr'
);
// If query is an empty string, return an empty array
if (query === '' || query === undefined) {
return res.status(200).json([]);
}
if (!service || !query || service === undefined || !service.apiKey) {
if (!app || !query || app === undefined || !app.apiKey) {
return res.status(400).json({
error: 'Wrong request',
});
}
const serviceUrl = new URL(service.url);
const appUrl = new URL(app.url);
const data = await axios
.get(`${serviceUrl.origin}/api/v1/search?query=${query}`, {
.get(`${appUrl.origin}/api/v1/search?query=${query}`, {
headers: {
// Set X-Api-Key to the value of the API key
'X-Api-Key': service.apiKey,
'X-Api-Key': app.apiKey,
},
})
.then((res) => res.data);

View File

@@ -2,7 +2,7 @@ import ping from 'ping';
import { NextApiRequest, NextApiResponse } from 'next';
async function Get(req: NextApiRequest, res: NextApiResponse) {
// Parse req.body as a ServiceItem
// Parse req.body as a AppItem
const { url } = req.query;
// Parse url as URL object
const parsedUrl = new URL(url as string);

View File

@@ -8,50 +8,50 @@ import { getConfig } from '../../../tools/getConfig';
import { Config } from '../../../tools/types';
async function Post(req: NextApiRequest, res: NextApiResponse) {
// Get the type of service from the request url
// Get the type of app from the request url
const configName = getCookie('config-name', { req });
const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props;
const qBittorrentServices = config.services.filter((service) => service.type === 'qBittorrent');
const delugeServices = config.services.filter((service) => service.type === 'Deluge');
const transmissionServices = config.services.filter((service) => service.type === 'Transmission');
const qBittorrentApp = config.apps.filter((app) => app.type === 'qBittorrent');
const delugeApp = config.apps.filter((app) => app.type === 'Deluge');
const transmissionApp = config.apps.filter((app) => app.type === 'Transmission');
const torrents: NormalizedTorrent[] = [];
if (!qBittorrentServices && !delugeServices && !transmissionServices) {
if (!qBittorrentApp && !delugeApp && !transmissionApp) {
return res.status(500).json({
statusCode: 500,
message: 'Missing services',
message: 'Missing apps',
});
}
try {
await Promise.all(
qBittorrentServices.map((service) =>
qBittorrentApp.map((apps) =>
new QBittorrent({
baseUrl: service.url,
username: service.username,
password: service.password,
baseUrl: apps.url,
username: apps.username,
password: apps.password,
})
.getAllData()
.then((e) => torrents.push(...e.torrents))
)
);
await Promise.all(
delugeServices.map((service) =>
delugeApp.map((apps) =>
new Deluge({
baseUrl: service.url,
password: 'password' in service ? service.password : '',
baseUrl: apps.url,
password: 'password' in apps ? apps.password : '',
})
.getAllData()
.then((e) => torrents.push(...e.torrents))
)
);
// Map transmissionServices
// Map transmissionApps
await Promise.all(
transmissionServices.map((service) =>
transmissionApp.map((apps) =>
new Transmission({
baseUrl: service.url,
username: 'username' in service ? service.username : '',
password: 'password' in service ? service.password : '',
baseUrl: apps.url,
username: 'username' in apps ? apps.username : '',
password: 'password' in apps ? apps.password : '',
})
.getAllData()
.then((e) => torrents.push(...e.torrents))

View File

@@ -11,7 +11,7 @@ import { UsenetHistoryItem } from '../../../../components/Dashboard/Tiles/UseNet
dayjs.extend(duration);
export interface UsenetHistoryRequestParams {
serviceId: string;
appId: string;
offset: number;
limit: number;
}
@@ -25,25 +25,25 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
const config = getConfig(configName?.toString() ?? 'default');
const { limit, offset, serviceId } = req.query as any as UsenetHistoryRequestParams;
const { limit, offset, appId } = req.query as any as UsenetHistoryRequestParams;
const service = config.services.find((x) => x.id === serviceId);
const app = config.apps.find((x) => x.id === appId);
if (!service) {
throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
if (!app) {
throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetHistoryResponse;
switch (service.integration?.type) {
switch (app.integration?.type) {
case 'nzbGet': {
const url = new URL(service.url);
const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
login:
service.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
hash:
service.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -77,11 +77,11 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
case 'sabnzbd': {
const { origin } = new URL(service.url);
const { origin } = new URL(app.url);
const apiKey = service.integration.properties.find((x) => x.field === 'apiKey')?.value;
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) {
throw new Error(`API Key for service "${service.name}" is missing`);
throw new Error(`API Key for app "${app.name}" is missing`);
}
const history = await new Client(origin, apiKey).history(offset, limit);
@@ -100,7 +100,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
throw new Error(`Service type "${service.integration?.type}" unrecognized.`);
throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);

View File

@@ -3,16 +3,14 @@ import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
import { Config } from '../../../../tools/types';
import { NzbgetStatus } from './nzbget/types';
import { NzbgetClient } from './nzbget/nzbget-client';
import { getConfig } from '../../../../tools/config/getConfig';
import { NzbgetClient } from './nzbget/nzbget-client';
import { NzbgetStatus } from './nzbget/types';
dayjs.extend(duration);
export interface UsenetInfoRequestParams {
serviceId: string;
appId: string;
}
export interface UsenetInfoResponse {
@@ -26,25 +24,25 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
const config = getConfig(configName?.toString() ?? 'default');
const { serviceId } = req.query as any as UsenetInfoRequestParams;
const { appId } = req.query as any as UsenetInfoRequestParams;
const service = config.services.find((x) => x.id === serviceId);
const app = config.apps.find((x) => x.id === appId);
if (!service) {
throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
if (!app) {
throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetInfoResponse;
switch (service.integration?.type) {
switch (app.integration?.type) {
case 'nzbGet': {
const url = new URL(service.url);
const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
login:
service.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
hash:
service.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -74,12 +72,12 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
case 'sabnzbd': {
const apiKey = service.integration.properties.find((x) => x.field === 'apiKey')?.value;
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) {
throw new Error(`API Key for service "${service.name}" is missing`);
throw new Error(`API Key for app "${app.name}" is missing`);
}
const { origin } = new URL(service.url);
const { origin } = new URL(app.url);
const queue = await new Client(origin, apiKey).queue(0, -1);
@@ -99,7 +97,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
throw new Error(`Service type "${service.integration?.type}" unrecognized.`);
throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);

View File

@@ -3,19 +3,19 @@ import { NzbgetClientOptions } from './types';
export function NzbgetClient(options: NzbgetClientOptions) {
if (!options?.host) {
throw new Error('Cannot connect to NZBGet. Missing host in service config.');
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 service config.');
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 service config.');
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 service config.');
throw new Error('Cannot connect to NZBGet. Missing password in app config.');
}
return new NZBGet(options);

View File

@@ -9,32 +9,32 @@ import { NzbgetClient } from './nzbget/nzbget-client';
dayjs.extend(duration);
export interface UsenetPauseRequestParams {
serviceId: string;
appId: string;
}
async function Post(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
const config = getConfig(configName?.toString() ?? 'default');
const { serviceId } = req.query as any as UsenetPauseRequestParams;
const { appId } = req.query as any as UsenetPauseRequestParams;
const service = config.services.find((x) => x.id === serviceId);
const app = config.apps.find((x) => x.id === appId);
if (!service) {
throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
if (!app) {
throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let result;
switch (service.integration?.type) {
switch (app.integration?.type) {
case 'nzbGet': {
const url = new URL(service.url);
const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
login:
service.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
hash:
service.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -51,18 +51,18 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
break;
}
case 'sabnzbd': {
const apiKey = service.integration.properties.find((x) => x.field === 'apiKey')?.value;
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) {
throw new Error(`API Key for service "${service.name}" is missing`);
throw new Error(`API Key for app "${app.name}" is missing`);
}
const { origin } = new URL(service.url);
const { origin } = new URL(app.url);
result = await new Client(origin, apiKey).queuePause();
break;
}
default:
throw new Error(`Service type "${service.integration?.type}" unrecognized.`);
throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(result);

View File

@@ -11,7 +11,7 @@ import { NzbgetQueueItem, NzbgetStatus } from './nzbget/types';
dayjs.extend(duration);
export interface UsenetQueueRequestParams {
serviceId: string;
appId: string;
offset: number;
limit: number;
}
@@ -25,25 +25,25 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
const config = getConfig(configName?.toString() ?? 'default');
const { limit, offset, serviceId } = req.query as any as UsenetQueueRequestParams;
const { limit, offset, appId } = req.query as any as UsenetQueueRequestParams;
const service = config.services.find((x) => x.id === serviceId);
const app = config.apps.find((x) => x.id === appId);
if (!service) {
throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
if (!app) {
throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let response: UsenetQueueResponse;
switch (service.integration?.type) {
switch (app.integration?.type) {
case 'nzbGet': {
const url = new URL(service.url);
const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
login:
service.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
hash:
service.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -93,12 +93,12 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
case 'sabnzbd': {
const apiKey = service.integration.properties.find((x) => x.field === 'apiKey')?.value;
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) {
throw new Error(`API Key for service "${service.name}" is missing`);
throw new Error(`API Key for app "${app.name}" is missing`);
}
const { origin } = new URL(service.url);
const { origin } = new URL(app.url);
const queue = await new Client(origin, apiKey).queue(offset, limit);
const items: UsenetQueueItem[] = queue.slots.map((slot) => {
@@ -126,7 +126,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
break;
}
default:
throw new Error(`Service type "${service.integration?.type}" unrecognized.`);
throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(response);

View File

@@ -4,14 +4,12 @@ import duration from 'dayjs/plugin/duration';
import { NextApiRequest, NextApiResponse } from 'next';
import { Client } from 'sabnzbd-api';
import { getConfig } from '../../../../tools/config/getConfig';
import { getServiceById } from '../../../../tools/hooks/useGetServiceByType';
import { Config } from '../../../../tools/types';
import { NzbgetClient } from './nzbget/nzbget-client';
dayjs.extend(duration);
export interface UsenetResumeRequestParams {
serviceId: string;
appId: string;
nzbId?: string;
}
@@ -19,25 +17,25 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
try {
const configName = getCookie('config-name', { req });
const config = getConfig(configName?.toString() ?? 'default');
const { serviceId } = req.query as any as UsenetResumeRequestParams;
const { appId } = req.query as any as UsenetResumeRequestParams;
const service = config.services.find((x) => x.id === serviceId);
const app = config.apps.find((x) => x.id === appId);
if (!service) {
throw new Error(`Service with ID "${req.query.serviceId}" could not be found.`);
if (!app) {
throw new Error(`App with ID "${req.query.appId}" could not be found.`);
}
let result;
switch (service.integration?.type) {
switch (app.integration?.type) {
case 'nzbGet': {
const url = new URL(service.url);
const url = new URL(app.url);
const options = {
host: url.hostname,
port: url.port,
login:
service.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
hash:
service.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
};
const nzbGet = NzbgetClient(options);
@@ -54,18 +52,18 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
break;
}
case 'sabnzbd': {
const apiKey = service.integration.properties.find((x) => x.field === 'apiKey')?.value;
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) {
throw new Error(`API Key for service "${service.name}" is missing`);
throw new Error(`API Key for app "${app.name}" is missing`);
}
const { origin } = new URL(service.url);
const { origin } = new URL(app.url);
result = await new Client(origin, apiKey).queueResume();
break;
}
default:
throw new Error(`Service type "${service.integration?.type}" unrecognized.`);
throw new Error(`App type "${app.integration?.type}" unrecognized.`);
}
return res.status(200).json(result);