import axios from 'axios'; import Consola from 'consola'; import { NextApiRequest, NextApiResponse } from 'next'; import { z } from 'zod'; import { AppIntegrationType, IntegrationType } from '../../../types/app'; import { getConfig } from '../../../tools/config/getConfig'; import { checkIntegrationsType } from '~/tools/client/app-properties'; export default async (req: NextApiRequest, res: NextApiResponse) => { // Filter out if the reuqest is a POST or a GET if (req.method === 'GET') { return Get(req, res); } return res.status(405).json({ statusCode: 405, message: 'Method not allowed', }); }; const getQuerySchema = z.object({ month: z .string() .regex(/^\d+$/) .transform((x) => parseInt(x, 10)), year: z .string() .regex(/^\d+$/) .transform((x) => parseInt(x, 10)), widgetId: z.string().uuid(), configName: z.string(), }); async function Get(req: NextApiRequest, res: NextApiResponse) { const parseResult = getQuerySchema.safeParse(req.query); if (!parseResult.success) { return res.status(400).json({ statusCode: 400, message: 'Invalid query parameters, please specify the widgetId, month, year and configName', }); } // Parse req.body as a AppItem const { month, year, widgetId, configName } = parseResult.data; const config = getConfig(configName); // Find the calendar widget in the config const calendar = config.widgets.find((w) => w.type === 'calendar' && w.id === widgetId); const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false; const mediaAppIntegrationTypes = [ 'sonarr', 'radarr', 'readarr', 'lidarr', ] as const satisfies readonly IntegrationType[]; const mediaApps = config.apps.filter((app) => checkIntegrationsType(app.integration, mediaAppIntegrationTypes) ); const IntegrationTypeEndpointMap = new Map([ ['sonarr', useSonarrv4 ? '/api/v3/calendar' : '/api/calendar'], ['radarr', '/api/v3/calendar'], ['lidarr', '/api/v1/calendar'], ['readarr', '/api/v1/calendar'], ]); try { const medias = await Promise.all( await mediaApps.map(async (app) => { const integration = app.integration!; const endpoint = IntegrationTypeEndpointMap.get(integration.type); if (!endpoint) { return { type: integration.type, items: [], success: false, }; } // Get the origin URL let { href: origin } = new URL(app.url); if (origin.endsWith('/')) { origin = origin.slice(0, -1); } const start = new Date(year, month - 1, 1); // First day of month const end = new Date(year, month, 0); // Last day of month const apiKey = integration.properties.find((x) => x.field === 'apiKey')?.value; if (!apiKey) return { type: integration.type, items: [], success: false }; return axios .get( `${origin}${endpoint}?apiKey=${apiKey}&end=${end.toISOString()}&start=${start.toISOString()}&includeSeries=true&includeEpisodeFile=true&includeEpisodeImages=true` ) .then((x) => ({ type: integration.type, items: x.data as any[], success: true })) .catch((err) => { Consola.error( `failed to process request to app '${integration.type}' (${app.id}): ${err}` ); return { type: integration.type, items: [], success: false, }; }); }) ); const results = await Promise.all(medias); const countFailed = results.filter((x) => !x.success).length; if (countFailed > 0) { Consola.warn(`A total of ${countFailed} apps for the calendar widget failed`); } return res.status(200).json({ tvShows: medias.filter((m) => m.type === 'sonarr').flatMap((m) => m.items), movies: medias.filter((m) => m.type === 'radarr').flatMap((m) => m.items), books: medias.filter((m) => m.type === 'readarr').flatMap((m) => m.items), musics: medias.filter((m) => m.type === 'lidarr').flatMap((m) => m.items), totalCount: medias.reduce((p, c) => p + c.items.length, 0), }); } catch (error) { Consola.error(`Error while requesting media from your app. Check your configuration. ${error}`); return res.status(500).json({ tvShows: [], movies: [], books: [], musics: [], totalCount: 0, }); } }