From 31ce44c452fac0b12270a16477eca85c2149abbf Mon Sep 17 00:00:00 2001 From: Pierre <63404022+0-Pierre@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:57:39 +0100 Subject: [PATCH] fix(mediarequests): changed lidarr notification and request to match new architecture This was using an older architecture for the notifications and request, this change updates the system to match how movies and tv are done --- server/api/musicbrainz/index.ts | 2 +- server/lib/notifications/agents/webpush.ts | 8 +- server/subscriber/MediaRequestSubscriber.ts | 117 +++++++++++++++----- server/subscriber/MediaSubscriber.ts | 5 + 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/server/api/musicbrainz/index.ts b/server/api/musicbrainz/index.ts index ab251cd0..75f1e5fd 100644 --- a/server/api/musicbrainz/index.ts +++ b/server/api/musicbrainz/index.ts @@ -66,7 +66,7 @@ class MusicBrainz extends ExternalAPI { public async getAlbum({ albumId, }: { - albumId: string; + albumId?: string; }): Promise { try { const data = await this.get( diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index fcb7416e..d44abb39 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -47,7 +47,9 @@ class WebPushAgent const mediaType = payload.media ? payload.media.mediaType === MediaType.MOVIE ? 'movie' - : 'series' + : payload.media.mediaType === MediaType.TV + ? 'series' + : 'album' : undefined; const is4k = payload.request?.is4k; @@ -119,7 +121,9 @@ class WebPushAgent const actionUrl = payload.issue ? `/issues/${payload.issue.id}` : payload.media - ? `/${payload.media.mediaType}/${payload.media.tmdbId}` + ? payload.media.mediaType === MediaType.MUSIC + ? `/music/${payload.media.mbId}` + : `/${payload.media.mediaType}/${payload.media.tmdbId}` : undefined; const actionUrlTitle = actionUrl diff --git a/server/subscriber/MediaRequestSubscriber.ts b/server/subscriber/MediaRequestSubscriber.ts index a8742990..2c6985c8 100644 --- a/server/subscriber/MediaRequestSubscriber.ts +++ b/server/subscriber/MediaRequestSubscriber.ts @@ -4,6 +4,8 @@ import type { AddSeriesOptions, SonarrSeries, } from '@server/api/servarr/sonarr'; +import LidarrAPI from '@server/api/servarr/lidarr'; +import MusicBrainz from '@server/api/musicbrainz' import SonarrAPI from '@server/api/servarr/sonarr'; import TheMovieDb from '@server/api/themoviedb'; import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; @@ -26,6 +28,7 @@ import type { InsertEvent, RemoveEvent, UpdateEvent, + Not } from 'typeorm'; import { EventSubscriber } from 'typeorm'; @@ -171,6 +174,65 @@ export class MediaRequestSubscriber } } + private async notifyAvailableMusic( + entity: MediaRequest, + event?: UpdateEvent + ) { + + // Get fresh media state using event manager + let latestMedia: Media | null = null; + if (event?.manager) { + latestMedia = await event.manager.findOne(Media, { + where: { id: entity.media.id }, + }); + } + if (!latestMedia) { + const mediaRepository = getRepository(Media); + latestMedia = await mediaRepository.findOne({ + where: { id: entity.media.id }, + }); + + if(!latestMedia + || latestMedia.mediaType !== MediaType.MUSIC + || latestMedia['status'] != MediaStatus.AVAILABLE) + { + return + } + + try { + const musicbrainz = new MusicBrainz(); + const albumDetails = await musicbrainz.getAlbum({ + albumId: latestMedia.mbId, + }); + + const coverImage = albumDetails.images?.find( + (img) => img.CoverType.toLowerCase() === 'cover' + )?.Url; + + notificationManager.sendNotification( + Notification.MEDIA_AVAILABLE, + { + event: `Album Request Now Available`, + notifyAdmin: false, + notifySystem: true, + notifyUser: entity.requestedBy, + subject: albumDetails.title ?? latestMedia.mbId ?? 'Unknown Album', + message: albumDetails.overview || 'Album is now available.', + media: latestMedia, + request: entity, + image: coverImage, + } + ); + } catch (e) { + logger.error('Something went wrong sending media notification(s)', { + label: 'Notifications', + errorMessage: e.message, + mediaId: entity.id, + }); + } + } + } + public async sendToRadarr(entity: MediaRequest): Promise { if ( entity.status === MediaRequestStatus.APPROVED && @@ -738,10 +800,10 @@ export class MediaRequestSubscriber } } - public async sendToLidarr(): Promise { + public async sendToLidarr(entity: MediaRequest): Promise { if ( - this.status !== MediaRequestStatus.APPROVED || - this.type !== MediaType.MUSIC + entity.status !== MediaRequestStatus.APPROVED || + entity.type !== MediaType.MUSIC ) { return; } @@ -750,7 +812,7 @@ export class MediaRequestSubscriber const mediaRepository = getRepository(Media); const settings = getSettings(); const media = await mediaRepository.findOne({ - where: { id: this.media.id }, + where: { id: entity.media.id }, relations: { requests: true }, }); @@ -759,21 +821,21 @@ export class MediaRequestSubscriber } const lidarrSettings = - this.serverId !== null && this.serverId >= 0 - ? settings.lidarr.find((l) => l.id === this.serverId) + entity.serverId !== null && entity.serverId >= 0 + ? settings.lidarr.find((l) => l.id === entity.serverId) : settings.lidarr.find((l) => l.isDefault); if (!lidarrSettings) { logger.warn('No valid Lidarr server configured', { label: 'Media Request', - requestId: this.id, - mediaId: this.media.id, + requestId: entity.id, + mediaId: entity.media.id, }); return; } - const rootFolder = this.rootFolder || lidarrSettings.activeDirectory; - const qualityProfile = this.profileId || lidarrSettings.activeProfileId; + const rootFolder = entity.rootFolder || lidarrSettings.activeDirectory; + const qualityProfile = entity.profileId || lidarrSettings.activeProfileId; const tags = lidarrSettings.tags?.map((t) => t.toString()) || []; const lidarr = new LidarrAPI({ @@ -881,26 +943,26 @@ export class MediaRequestSubscriber logger.error('Failed final album monitoring check', { label: 'Media Request', error: err.message, - requestId: this.id, - mediaId: this.media.id, - albumId: album.id, + requestId: entity.id, + mediaId: entity.media.id, + albumId: entity.id, }); } }, 20000); logger.info('Completed album monitoring setup', { label: 'Media Request', - requestId: this.id, - mediaId: this.media.id, - albumId: album.id, + requestId: entity.id, + mediaId: entity.media.id, + albumId: entity.id, }); } catch (err) { logger.error('Failed to process album monitoring', { label: 'Media Request', error: err.message, - requestId: this.id, - mediaId: this.media.id, - albumId: album.id, + requestId: entity.id, + mediaId: entity.media.id, + albumId: entity.id, }); } }, 60000); @@ -934,8 +996,8 @@ export class MediaRequestSubscriber logger.error('Failed to process existing album', { label: 'Media Request', error: err.message, - requestId: this.id, - mediaId: this.media.id, + requestId: entity.id, + mediaId: entity.media.id, albumId: existingAlbum.id, }); } @@ -943,9 +1005,9 @@ export class MediaRequestSubscriber } } else { const requestRepository = getRepository(MediaRequest); - this.status = MediaRequestStatus.FAILED; + entity.status = MediaRequestStatus.FAILED; await requestRepository.save(this); - this.sendNotification(media, Notification.MEDIA_FAILED); + MediaRequest.sendNotification(entity, media, Notification.MEDIA_FAILED); throw error; } } @@ -953,8 +1015,8 @@ export class MediaRequestSubscriber logger.error('Failed to process Lidarr request', { label: 'Media Request', error: e.message, - requestId: this.id, - mediaId: this.media.id, + requestId: entity.id, + mediaId: entity.media.id, }); } } @@ -1060,6 +1122,7 @@ export class MediaRequestSubscriber this.sendToRadarr(event.entity as MediaRequest); this.sendToSonarr(event.entity as MediaRequest); + this.sendToLidarr(event.entity as MediaRequest); this.updateParentStatus(event.entity as MediaRequest); @@ -1070,6 +1133,9 @@ export class MediaRequestSubscriber if (event.entity.media.mediaType === MediaType.TV) { this.notifyAvailableSeries(event.entity as MediaRequest, event); } + if (event.entity.media.mediaType === MediaType.MUSIC) { + this.notifyAvailableMusic(event.entity as MediaRequest, event); + } } } @@ -1080,6 +1146,7 @@ export class MediaRequestSubscriber this.sendToRadarr(event.entity as MediaRequest); this.sendToSonarr(event.entity as MediaRequest); + this.sendToLidarr(event.entity as MediaRequest); this.updateParentStatus(event.entity as MediaRequest); } diff --git a/server/subscriber/MediaSubscriber.ts b/server/subscriber/MediaSubscriber.ts index 3cf8229f..1f860940 100644 --- a/server/subscriber/MediaSubscriber.ts +++ b/server/subscriber/MediaSubscriber.ts @@ -109,6 +109,11 @@ export class MediaSubscriber implements EntitySubscriberInterface { const allSeasonsReady = allSeasonResults.every((result) => result); shouldComplete = allSeasonsReady; + } else if (event.mediaType === MediaType.MUSIC) + { + if(event['status'] == MediaStatus.AVAILABLE || event['status'] === MediaStatus.DELETED) { + shouldComplete = true; + } } if (shouldComplete) {