fix: resolved issues with the music slider displaying all menus, and ensured media are properly removed from Lidarr.

This commit is contained in:
Pierre
2025-01-18 14:31:31 +01:00
committed by HiItsStolas
parent a190320abd
commit fe37a1de98
7 changed files with 303 additions and 48 deletions

View File

@@ -171,7 +171,7 @@ class Media {
})
public mediaAddedAt: Date;
@Column({ nullable: true, type: 'int' })
@Column({ nullable: false, type: 'int', default: 0 })
public serviceId?: number | null;
@Column({ nullable: true, type: 'int' })

View File

@@ -566,26 +566,13 @@ class BaseScanner<T> {
? MediaStatus.PROCESSING
: MediaStatus.AVAILABLE;
newMedia.mediaType = MediaType.MUSIC;
if (mediaAddedAt) {
newMedia.mediaAddedAt = mediaAddedAt;
}
if (ratingKey) {
newMedia.ratingKey = ratingKey;
}
if (serviceId) {
newMedia.serviceId = serviceId;
}
if (externalServiceId) {
newMedia.externalServiceId = externalServiceId;
}
if (externalServiceSlug) {
newMedia.externalServiceSlug = externalServiceSlug;
}
newMedia.mediaAddedAt = mediaAddedAt ?? newMedia.mediaAddedAt;
newMedia.ratingKey = ratingKey ?? newMedia.ratingKey;
newMedia.serviceId = serviceId ?? newMedia.serviceId;
newMedia.externalServiceId =
externalServiceId ?? newMedia.externalServiceId;
newMedia.externalServiceSlug =
externalServiceSlug ?? newMedia.externalServiceSlug;
try {
await mediaRepository.save(newMedia);
@@ -596,6 +583,41 @@ class BaseScanner<T> {
error: err.message,
});
}
} else {
let hasChanges = false;
if (serviceId && !existing.serviceId) {
existing.serviceId = serviceId;
hasChanges = true;
}
if (externalServiceId && !existing.externalServiceId) {
existing.externalServiceId = externalServiceId;
hasChanges = true;
}
if (externalServiceSlug && !existing.externalServiceSlug) {
existing.externalServiceSlug = externalServiceSlug;
hasChanges = true;
}
if (mediaAddedAt && !existing.mediaAddedAt) {
existing.mediaAddedAt = mediaAddedAt;
hasChanges = true;
}
if (ratingKey && !existing.ratingKey) {
existing.ratingKey = ratingKey;
hasChanges = true;
}
if (hasChanges) {
try {
await mediaRepository.save(existing);
this.log(`Updated existing media: ${title}`);
} catch (err) {
this.log('Failed to update existing media', 'error', {
title,
error: err.message,
});
}
}
}
});
}

View File

@@ -41,7 +41,6 @@ class LidarrScanner
const sessionId = this.startRun();
try {
// Filter out duplicate servers
this.servers = uniqWith(settings.lidarr, (lidarrA, lidarrB) => {
return (
lidarrA.hostname === lidarrB.hostname &&
@@ -81,14 +80,10 @@ class LidarrScanner
private async processLidarrAlbum(lidarrAlbum: LidarrAlbum): Promise<void> {
try {
if (!lidarrAlbum.monitored) {
this.log('Title is unmonitored. Skipping item.', 'debug', {
title: lidarrAlbum.title,
});
return;
}
const mbId = lidarrAlbum.foreignAlbumId;
if (!mbId) {
this.log(
'No MusicBrainz ID found for this title. Skipping item.',
@@ -103,7 +98,7 @@ class LidarrScanner
await this.processMusic(mbId, {
serviceId: this.currentServer.id,
externalServiceId: lidarrAlbum.id,
externalServiceSlug: lidarrAlbum.titleSlug,
externalServiceSlug: mbId,
title: lidarrAlbum.title,
processing:
lidarrAlbum.monitored &&

View File

@@ -26,12 +26,15 @@ musicRoutes.get('/:id', async (req, res, next) => {
const [media, onUserWatchlist] = await Promise.all([
getRepository(Media)
.findOne({
where: {
mbId: req.params.id,
mediaType: MediaType.MUSIC,
},
.createQueryBuilder('media')
.leftJoinAndSelect('media.requests', 'requests')
.leftJoinAndSelect('requests.requestedBy', 'requestedBy')
.leftJoinAndSelect('requests.modifiedBy', 'modifiedBy')
.where({
mbId: req.params.id,
mediaType: MediaType.MUSIC,
})
.getOne()
.then((media) => media ?? undefined),
getRepository(Watchlist).exist({

View File

@@ -738,6 +738,226 @@ export class MediaRequestSubscriber
}
}
public async sendToLidarr(): Promise<void> {
if (
this.status !== MediaRequestStatus.APPROVED ||
this.type !== MediaType.MUSIC
) {
return;
}
try {
const mediaRepository = getRepository(Media);
const settings = getSettings();
const media = await mediaRepository.findOne({
where: { id: this.media.id },
relations: { requests: true },
});
if (!media?.mbId) {
throw new Error('Media data or MusicBrainz ID not found');
}
const lidarrSettings =
this.serverId !== null && this.serverId >= 0
? settings.lidarr.find((l) => l.id === this.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,
});
return;
}
const rootFolder = this.rootFolder || lidarrSettings.activeDirectory;
const qualityProfile = this.profileId || lidarrSettings.activeProfileId;
const tags = lidarrSettings.tags?.map((t) => t.toString()) || [];
const lidarr = new LidarrAPI({
apiKey: lidarrSettings.apiKey,
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
});
const lidarrAlbum = await lidarr.getAlbumByMusicBrainzId(media.mbId);
let artistId: number;
try {
const existingArtist = await lidarr.getArtistByMusicBrainzId(
lidarrAlbum.artist.foreignArtistId
);
artistId = existingArtist.id;
} catch {
const addedArtist = await lidarr.addArtist({
artistName: lidarrAlbum.artist.artistName,
foreignArtistId: lidarrAlbum.artist.foreignArtistId,
qualityProfileId: qualityProfile,
profileId: qualityProfile,
metadataProfileId: qualityProfile,
rootFolderPath: rootFolder,
monitored: false,
tags: tags.map((t) => Number(t)),
searchNow: !lidarrSettings.preventSearch,
monitorNewItems: 'none',
monitor: 'none',
searchForMissingAlbums: false,
addOptions: {
monitor: 'none',
monitored: false,
searchForMissingAlbums: false,
},
});
await new Promise((resolve) => setTimeout(resolve, 60000));
artistId = addedArtist.id;
}
try {
const album = await lidarr.addAlbum({
mbId: media.mbId,
foreignAlbumId: media.mbId,
title: lidarrAlbum.title,
qualityProfileId: qualityProfile,
profileId: qualityProfile,
metadataProfileId: qualityProfile,
rootFolderPath: rootFolder,
monitored: false,
tags,
searchNow: false,
artistId,
images: lidarrAlbum.images?.length
? lidarrAlbum.images
: [
{
url: '',
coverType: 'cover',
},
],
addOptions: {
monitor: 'none',
monitored: false,
searchForMissingAlbums: false,
},
artist: {
id: artistId,
foreignArtistId: lidarrAlbum.artist.foreignArtistId,
artistName: lidarrAlbum.artist.artistName,
qualityProfileId: qualityProfile,
metadataProfileId: qualityProfile,
rootFolderPath: rootFolder,
monitored: false,
monitorNewItems: 'none',
},
});
media.externalServiceId = album.id;
(media.externalServiceSlug = media.mbId),
(media.serviceId = lidarrSettings.id);
media.status = MediaStatus.PROCESSING;
await mediaRepository.save(media);
setTimeout(async () => {
try {
const albumDetails = await lidarr.getAlbum({ id: album.id });
albumDetails.monitored = true;
await lidarr.updateAlbum(albumDetails);
if (!lidarrSettings.preventSearch) {
await lidarr.searchAlbum(album.id);
}
setTimeout(async () => {
try {
const finalAlbumDetails = await lidarr.getAlbum({
id: album.id,
});
if (!finalAlbumDetails.monitored) {
finalAlbumDetails.monitored = true;
await lidarr.updateAlbum(finalAlbumDetails);
await lidarr.searchAlbum(album.id);
}
} catch (err) {
logger.error('Failed final album monitoring check', {
label: 'Media Request',
error: err.message,
requestId: this.id,
mediaId: this.media.id,
albumId: album.id,
});
}
}, 20000);
logger.info('Completed album monitoring setup', {
label: 'Media Request',
requestId: this.id,
mediaId: this.media.id,
albumId: album.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,
});
}
}, 60000);
} catch (error) {
if (error.message.includes('This album has already been added')) {
const existingAlbums = await lidarr.getAlbums();
const existingAlbum = existingAlbums.find(
(a) => a.foreignAlbumId === media.mbId
);
if (existingAlbum) {
media.externalServiceId = existingAlbum.id;
media.externalServiceSlug = media.mbId;
media.serviceId = lidarrSettings.id;
media.status = MediaStatus.PROCESSING;
await mediaRepository.save(media);
setTimeout(async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 20000));
const albumDetails = await lidarr.getAlbum({
id: existingAlbum.id,
});
albumDetails.monitored = true;
await lidarr.updateAlbum(albumDetails);
if (!lidarrSettings.preventSearch) {
await lidarr.searchAlbum(existingAlbum.id);
}
} catch (err) {
logger.error('Failed to process existing album', {
label: 'Media Request',
error: err.message,
requestId: this.id,
mediaId: this.media.id,
albumId: existingAlbum.id,
});
}
}, 0);
}
} else {
const requestRepository = getRepository(MediaRequest);
this.status = MediaRequestStatus.FAILED;
await requestRepository.save(this);
this.sendNotification(media, Notification.MEDIA_FAILED);
throw error;
}
}
} catch (e) {
logger.error('Failed to process Lidarr request', {
label: 'Media Request',
error: e.message,
requestId: this.id,
mediaId: this.media.id,
});
}
}
public async updateParentStatus(entity: MediaRequest): Promise<void> {
const mediaRepository = getRepository(Media);
const media = await mediaRepository.findOne({

View File

@@ -68,6 +68,7 @@ const messages = defineMessages('components.ManageSlideOver', {
playedby: 'Played By',
movie: 'movie',
tvshow: 'series',
album: 'album',
});
const isMovie = (
@@ -515,9 +516,16 @@ const ManageSlideOver = ({
mediaType: intl.formatMessage(
mediaType === 'movie'
? messages.movie
: mediaType === 'music'
? messages.album
: messages.tvshow
),
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
arr:
mediaType === 'movie'
? 'Radarr'
: mediaType === 'music'
? 'Lidarr'
: 'Sonarr',
}
)}
</div>
@@ -742,7 +750,11 @@ const ManageSlideOver = ({
<div className="mt-2 text-xs text-gray-400">
{intl.formatMessage(messages.manageModalClearMediaWarning, {
mediaType: intl.formatMessage(
mediaType === 'movie' ? messages.movie : messages.tvshow
mediaType === 'movie'
? messages.movie
: mediaType === 'music'
? messages.album
: messages.tvshow
),
mediaServerName:
settings.currentSettings.mediaServerType ===

View File

@@ -78,20 +78,23 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
return (
<div className="block">
<RequestModal
show={showEditModal}
tmdbId={request.media.tmdbId}
type={request.type}
is4k={request.is4k}
editRequest={request}
onCancel={() => setShowEditModal(false)}
onComplete={() => {
if (onUpdate) {
onUpdate();
}
setShowEditModal(false);
}}
/>
{request.media && (
<RequestModal
show={showEditModal}
tmdbId={request.type === 'music' ? undefined : request.media.tmdbId}
mbId={request.type === 'music' ? request.media.mbId : undefined}
type={request.type}
is4k={request.is4k}
editRequest={request}
onCancel={() => setShowEditModal(false)}
onComplete={() => {
if (onUpdate) {
onUpdate();
}
setShowEditModal(false);
}}
/>
)}
<div className="px-4 py-3 text-gray-300">
<div className="flex items-center justify-between">
<div className="mr-6 min-w-0 flex-1 flex-col items-center text-sm leading-5">