mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
🏗️ Migrate overseerr media requests to tRPC
This commit is contained in:
@@ -1,17 +1,16 @@
|
|||||||
import { Alert, Button, Checkbox, createStyles, Group, Modal, Stack, Table } from '@mantine/core';
|
import { Alert, Button, Checkbox, createStyles, Group, Modal, Stack, Table } from '@mantine/core';
|
||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons-react';
|
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons-react';
|
||||||
import axios from 'axios';
|
|
||||||
import Consola from 'consola';
|
import Consola from 'consola';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useConfigContext } from '~/config/provider';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
import { useColorTheme } from '../../tools/color';
|
import { useColorTheme } from '../../tools/color';
|
||||||
import { MovieResult } from './Movie.d';
|
import { MovieResult } from './Movie.d';
|
||||||
import { MediaType, Result } from './SearchResult.d';
|
import { Result } from './SearchResult.d';
|
||||||
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
|
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
|
||||||
import { api } from '~/utils/api';
|
|
||||||
import { useConfigContext } from '~/config/provider';
|
|
||||||
|
|
||||||
interface RequestModalProps {
|
interface RequestModalProps {
|
||||||
base: Result;
|
base: Result;
|
||||||
@@ -62,6 +61,7 @@ export function MovieRequestModal({
|
|||||||
setOpened: (opened: boolean) => void;
|
setOpened: (opened: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
|
const requestMediaAsync = useMediaRequestMutation();
|
||||||
const { t } = useTranslation('modules/overseerr');
|
const { t } = useTranslation('modules/overseerr');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -97,7 +97,7 @@ export function MovieRequestModal({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
askForMedia('movie', result.id, result.title, []);
|
requestMediaAsync('movie', result.id, result.title);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('popup.item.buttons.request')}
|
{t('popup.item.buttons.request')}
|
||||||
@@ -120,6 +120,7 @@ export function TvRequestModal({
|
|||||||
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const { t } = useTranslation('modules/overseerr');
|
const { t } = useTranslation('modules/overseerr');
|
||||||
|
const requestMediaAsync = useMediaRequestMutation();
|
||||||
|
|
||||||
const toggleRow = (container: TvShowResultSeason) =>
|
const toggleRow = (container: TvShowResultSeason) =>
|
||||||
setSelection((current: TvShowResultSeason[]) =>
|
setSelection((current: TvShowResultSeason[]) =>
|
||||||
@@ -200,7 +201,7 @@ export function TvRequestModal({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={selection.length === 0}
|
disabled={selection.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
askForMedia(
|
requestMediaAsync(
|
||||||
'tv',
|
'tv',
|
||||||
result.id,
|
result.id,
|
||||||
result.name,
|
result.name,
|
||||||
@@ -216,7 +217,11 @@ export function TvRequestModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function askForMedia(type: MediaType, id: number, name: string, seasons?: number[]) {
|
const useMediaRequestMutation = () => {
|
||||||
|
const { name: configName } = useConfigContext();
|
||||||
|
const { mutateAsync } = api.overseerr.request.useMutation();
|
||||||
|
|
||||||
|
return async (type: 'tv' | 'movie', id: number, name: string, seasons?: number[]) => {
|
||||||
Consola.info(`Requesting ${type} ${id} ${name}`);
|
Consola.info(`Requesting ${type} ${id} ${name}`);
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Request',
|
title: 'Request',
|
||||||
@@ -228,9 +233,16 @@ function askForMedia(type: MediaType, id: number, name: string, seasons?: number
|
|||||||
withCloseButton: false,
|
withCloseButton: false,
|
||||||
icon: <IconAlertCircle />,
|
icon: <IconAlertCircle />,
|
||||||
});
|
});
|
||||||
axios
|
|
||||||
.post(`/api/modules/overseerr/${id}`, { type, seasons })
|
await mutateAsync(
|
||||||
.then(() => {
|
{
|
||||||
|
configName: configName!,
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
seasons: seasons ?? [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
updateNotification({
|
updateNotification({
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
title: '',
|
title: '',
|
||||||
@@ -239,8 +251,8 @@ function askForMedia(type: MediaType, id: number, name: string, seasons?: number
|
|||||||
icon: <IconCheck />,
|
icon: <IconCheck />,
|
||||||
autoClose: 2000,
|
autoClose: 2000,
|
||||||
});
|
});
|
||||||
})
|
},
|
||||||
.catch((err) => {
|
onError: (err) => {
|
||||||
updateNotification({
|
updateNotification({
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
color: 'red',
|
color: 'red',
|
||||||
@@ -248,5 +260,8 @@ function askForMedia(type: MediaType, id: number, name: string, seasons?: number
|
|||||||
message: err.message,
|
message: err.message,
|
||||||
autoClose: 2000,
|
autoClose: 2000,
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -104,4 +104,66 @@ export const overseerrRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
return tv;
|
return tv;
|
||||||
}),
|
}),
|
||||||
|
request: publicProcedure
|
||||||
|
.input(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
configName: z.string(),
|
||||||
|
id: z.number(),
|
||||||
|
})
|
||||||
|
.and(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
seasons: z.array(z.number()),
|
||||||
|
type: z.literal('tv'),
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
z.object({
|
||||||
|
type: z.literal('movie'),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const config = getConfig(input.configName);
|
||||||
|
const app = config.apps.find(
|
||||||
|
(app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
|
||||||
|
);
|
||||||
|
const apiKey = app?.integration?.properties.find((x) => x.field === 'apiKey')?.value;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'No app found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const appUrl = new URL(app.url);
|
||||||
|
Consola.info('Got an Overseerr request with these arguments', {
|
||||||
|
mediaType: input.type,
|
||||||
|
mediaId: input.id,
|
||||||
|
seasons: input.type === 'tv' ? input.seasons : undefined,
|
||||||
|
});
|
||||||
|
return axios
|
||||||
|
.post(
|
||||||
|
`${appUrl.origin}/api/v1/request`,
|
||||||
|
{
|
||||||
|
mediaType: input.type,
|
||||||
|
mediaId: input.id,
|
||||||
|
seasons: input.type === 'tv' ? input.seasons : undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
// Set X-Api-Key to the value of the API key
|
||||||
|
'X-Api-Key': apiKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch((err) => {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user