From 97f9c2d6c9dd1c8eec6f8a06833f5423e3e8975f Mon Sep 17 00:00:00 2001 From: Pierre <63404022+0-Pierre@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:41:47 +0100 Subject: [PATCH] fix: properly pass qualityProfile and metadataProfile in music requests based on Lidarr configuration selection --- seerr-api.yml | 6 ++ server/api/servarr/lidarr.ts | 27 +++----- server/lib/settings/index.ts | 7 +- server/routes/service.ts | 6 +- server/routes/settings/lidarr.ts | 3 +- server/subscriber/MediaRequestSubscriber.ts | 27 +++++--- src/components/Settings/LidarrModal/index.tsx | 68 +++++++++++++++++++ src/i18n/locale/en.json | 5 ++ 8 files changed, 120 insertions(+), 29 deletions(-) diff --git a/seerr-api.yml b/seerr-api.yml index dc87ea15..46584df1 100644 --- a/seerr-api.yml +++ b/seerr-api.yml @@ -727,6 +727,12 @@ components: activeProfileName: type: string example: 128kps + activeMetadataProfileId: + type: number + example: 1 + activeMetadataProfileName: + type: string + example: Standard activeDirectory: type: string example: '/music/' diff --git a/server/api/servarr/lidarr.ts b/server/api/servarr/lidarr.ts index a9fc2510..bd4f4963 100644 --- a/server/api/servarr/lidarr.ts +++ b/server/api/servarr/lidarr.ts @@ -278,6 +278,11 @@ export interface SearchCommand extends Record { albumIds: number[]; } +export interface MetadataProfile { + id: number; + name: string; +} + class LidarrAPI extends ServarrBase<{ albumId: number }> { protected apiKey: string; constructor({ url, apiKey }: { url: string; apiKey: string }) { @@ -368,25 +373,13 @@ class LidarrAPI extends ServarrBase<{ albumId: number }> { } } - public async searchOnAdd(albumId: number): Promise { - logger.info('Executing album search command', { - label: 'Lidarr API', - albumId, - }); - + public async getMetadataProfiles(): Promise { try { - await this.post('/command', { - name: 'AlbumSearch', - albumIds: [albumId], - }); + const data = await this.get('/metadataProfile'); + return data; } catch (e) { - logger.error( - 'Something went wrong while executing Lidarr album search.', - { - label: 'Lidarr API', - errorMessage: e.message, - albumId, - } + throw new Error( + `[Lidarr] Failed to retrieve metadata profiles: ${e.message}` ); } } diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 74fe2ec9..241b9f8e 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -83,8 +83,6 @@ export interface RadarrSettings extends DVRSettings { minimumAvailability: string; } -export type LidarrSettings = DVRSettings; - export interface SonarrSettings extends DVRSettings { seriesType: 'standard' | 'daily' | 'anime'; animeSeriesType: 'standard' | 'daily' | 'anime'; @@ -97,6 +95,11 @@ export interface SonarrSettings extends DVRSettings { enableSeasonFolders: boolean; } +export interface LidarrSettings extends DVRSettings { + activeMetadataProfileId?: number; + activeMetadataProfileName?: string; +} + interface Quota { quotaLimit?: number; quotaDays?: number; diff --git a/server/routes/service.ts b/server/routes/service.ts index d51d9b50..20fe5f82 100644 --- a/server/routes/service.ts +++ b/server/routes/service.ts @@ -251,8 +251,9 @@ serviceRoutes.get<{ id: string }>('/lidarr/:id', async (req, res, next) => { }); try { - const [profiles, rootFolders, tags] = await Promise.all([ + const [profiles, metadataProfiles, rootFolders, tags] = await Promise.all([ lidarr.getProfiles(), + lidarr.getMetadataProfiles(), lidarr.getRootFolders(), lidarr.getTags(), ]); @@ -264,8 +265,11 @@ serviceRoutes.get<{ id: string }>('/lidarr/:id', async (req, res, next) => { isDefault: lidarrSettings.isDefault, activeDirectory: lidarrSettings.activeDirectory, activeProfileId: lidarrSettings.activeProfileId, + activeMetadataProfileId: lidarrSettings.activeMetadataProfileId, + activeTags: lidarrSettings.tags ?? [], }, profiles, + metadataProfiles, rootFolders: rootFolders.map((folder) => ({ id: folder.id, path: folder.path, diff --git a/server/routes/settings/lidarr.ts b/server/routes/settings/lidarr.ts index e9e1f81d..d852be2b 100644 --- a/server/routes/settings/lidarr.ts +++ b/server/routes/settings/lidarr.ts @@ -42,11 +42,13 @@ lidarrRoutes.post< .then((value) => value.urlBase) .catch(() => req.body.baseUrl); const profiles = await lidarr.getProfiles(); + const metadataProfiles = await lidarr.getMetadataProfiles(); const folders = await lidarr.getRootFolders(); const tags = await lidarr.getTags(); return res.status(200).json({ profiles, + metadataProfiles, rootFolders: folders.map((folder) => ({ id: folder.id, path: folder.path, @@ -59,7 +61,6 @@ lidarrRoutes.post< label: 'Lidarr', message: e.message, }); - next({ status: 500, message: 'Failed to connect to Lidarr' }); } }); diff --git a/server/subscriber/MediaRequestSubscriber.ts b/server/subscriber/MediaRequestSubscriber.ts index 14121bc4..aef90c0c 100644 --- a/server/subscriber/MediaRequestSubscriber.ts +++ b/server/subscriber/MediaRequestSubscriber.ts @@ -1,6 +1,7 @@ import CoverArtArchive from '@server/api/coverartarchive'; import ListenBrainzAPI from '@server/api/listenbrainz'; import MusicBrainz from '@server/api/musicbrainz'; +import type { LidarrAlbumOptions } from '@server/api/servarr/lidarr'; import LidarrAPI from '@server/api/servarr/lidarr'; import type { RadarrMovieOptions } from '@server/api/servarr/radarr'; import RadarrAPI from '@server/api/servarr/radarr'; @@ -915,6 +916,21 @@ export class MediaRequestSubscriber }); } + let qualityProfile = lidarrSettings.activeProfileId; + const metadataProfile = lidarrSettings.activeMetadataProfileId ?? 1; + + if (entity.profileId && entity.profileId !== qualityProfile) { + qualityProfile = entity.profileId; + logger.info( + `Request has an override quality profile ID: ${qualityProfile}`, + { + label: 'Media Request', + requestId: entity.id, + mediaId: entity.media.id, + } + ); + } + const artistPath = `${rootFolder}/${albumInfo.artist.artistName}`; const addAlbumPayload: LidarrAlbumOptions = { @@ -925,7 +941,7 @@ export class MediaRequestSubscriber foreignAlbumId: albumInfo.foreignAlbumId, monitored: true, anyReleaseOk: true, - profileId: 1, + profileId: qualityProfile, duration: albumInfo.duration || 0, albumType: albumInfo.albumType, secondaryTypes: [], @@ -948,8 +964,8 @@ export class MediaRequestSubscriber links: albumInfo.artist.links || [], images: albumInfo.artist.images || [], path: artistPath, - qualityProfileId: 1, - metadataProfileId: 2, + qualityProfileId: qualityProfile, + metadataProfileId: metadataProfile, monitored: true, monitorNewItems: 'none', rootFolderPath: rootFolder, @@ -978,15 +994,10 @@ export class MediaRequestSubscriber }; await mediaRepository.update({ id: entity.media.id }, updateFields); - - if (addAlbumPayload.addOptions.searchForNewAlbum) { - await lidarr.searchOnAdd(result.id); - } }) .catch(async (error) => { const requestRepository = getRepository(MediaRequest); - entity.status = MediaRequestStatus.FAILED; await requestRepository.update( { id: entity.id }, { status: MediaRequestStatus.FAILED } diff --git a/src/components/Settings/LidarrModal/index.tsx b/src/components/Settings/LidarrModal/index.tsx index 132917ed..c3a980ee 100644 --- a/src/components/Settings/LidarrModal/index.tsx +++ b/src/components/Settings/LidarrModal/index.tsx @@ -59,6 +59,11 @@ const messages = defineMessages('components.Settings.LidarrModal', { tags: 'Tags', notagoptions: 'No tags.', selecttags: 'Select tags', + metadataprofile: 'Metadata Profile', + selectMetadataProfile: 'Select metadata profile', + loadingmetadataprofiles: 'Loading metadata profiles…', + testFirstMetadataProfiles: 'Test connection to load metadata profiles', + validationMetadataProfileRequired: 'You must select a metadata profile', }); interface TestResponse { @@ -66,6 +71,10 @@ interface TestResponse { id: number; name: string; }[]; + metadataProfiles: { + id: number; + name: string; + }[]; rootFolders: { id: number; path: string; @@ -91,6 +100,7 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => { const [isTesting, setIsTesting] = useState(false); const [testResponse, setTestResponse] = useState({ profiles: [], + metadataProfiles: [], rootFolders: [], tags: [], }); @@ -134,6 +144,9 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => { intl.formatMessage(messages.validationBaseUrlTrailingSlash), (value) => !value || !value.endsWith('/') ), + activeMetadataProfileId: Yup.string().required( + intl.formatMessage(messages.validationMetadataProfileRequired) + ), }); const testConnection = useCallback( @@ -236,6 +249,7 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => { syncEnabled: lidarr?.syncEnabled ?? false, enableSearch: !lidarr?.preventSearch, tagRequests: lidarr?.tagRequests ?? false, + activeMetadataProfileId: lidarr?.activeMetadataProfileId ?? 1, }} validationSchema={LidarrSettingsSchema} onSubmit={async (values) => { @@ -260,6 +274,11 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => { syncEnabled: values.syncEnabled, preventSearch: !values.enableSearch, tagRequests: values.tagRequests, + activeMetadataProfileId: Number(values.activeMetadataProfileId), + activeMetadataProfileName: testResponse.metadataProfiles.find( + (profile) => + profile.id === Number(values.activeMetadataProfileId) + )?.name, }; const response = await fetch( @@ -569,6 +588,55 @@ const LidarrModal = ({ onClose, lidarr, onSave }: LidarrModalProps) => { )} +
+ +
+
+ + + {testResponse.metadataProfiles.length > 0 && + testResponse.metadataProfiles.map((profile) => ( + + ))} + +
+ {errors.activeMetadataProfileId && + touched.activeMetadataProfileId && + typeof errors.activeMetadataProfileId === 'string' && ( +
+ {errors.activeMetadataProfileId} +
+ )} +
+