diff --git a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs index 51e920b15..09b7d9858 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs @@ -1,4 +1,5 @@ using System.Text; +using Ombi.Helpers; namespace Ombi.Schedule.Jobs.Ombi { @@ -22,13 +23,20 @@ namespace Ombi.Schedule.Jobs.Ombi protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url) { - sb.Append(""); - sb.Append(""); - sb.AppendFormat("", mediaurl); - sb.AppendFormat("", url); - sb.Append(""); - sb.Append(""); - sb.Append(""); + if (url.HasValue()) + { + sb.Append(""); + sb.Append( + ""); + sb.AppendFormat("", mediaurl); + sb.AppendFormat( + "", + url); + sb.Append(""); + sb.Append(""); + sb.Append(""); + } + sb.Append(""); sb.Append(""); } @@ -44,9 +52,9 @@ namespace Ombi.Schedule.Jobs.Ombi { sb.Append(""); sb.Append(""); - sb.AppendFormat("", url); + if(url.HasValue()) sb.AppendFormat("", url); sb.AppendFormat("

{0}

", title); - sb.Append("
"); + if (url.HasValue()) sb.Append(""); sb.Append(""); sb.Append(""); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 6e89d167e..f152f6b4b 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -9,6 +9,8 @@ using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; @@ -18,6 +20,7 @@ using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Notifications.Templates; using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -29,7 +32,8 @@ namespace Ombi.Schedule.Jobs.Ombi public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, - UserManager um, ISettingsService newsletter, ILogger log) + UserManager um, ISettingsService newsletter, ILogger log, + ILidarrApi lidarrApi, IRepository albumCache, ISettingsService lidarrSettings) { _plex = plex; _emby = emby; @@ -46,6 +50,10 @@ namespace Ombi.Schedule.Jobs.Ombi _customizationSettings.ClearCache(); _newsletterSettings.ClearCache(); _log = log; + _lidarrApi = lidarrApi; + _lidarrAlbumRepository = albumCache; + _lidarrSettings = lidarrSettings; + _lidarrSettings.ClearCache(); } private readonly IPlexContentRepository _plex; @@ -60,6 +68,9 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly ISettingsService _newsletterSettings; private readonly UserManager _userManager; private readonly ILogger _log; + private readonly ILidarrApi _lidarrApi; + private readonly IRepository _lidarrAlbumRepository; + private readonly ISettingsService _lidarrSettings; public async Task Start(NewsletterSettings settings, bool test) { @@ -87,21 +98,26 @@ namespace Ombi.Schedule.Jobs.Ombi // Get the Content var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); + var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking(); var addedLog = _recentlyAddedLog.GetAll(); var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId); var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); + // Filter out the ones that we haven't sent yet var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); + var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet(); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); + _log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count()); var plexEpisodesToSend = FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds); @@ -117,11 +133,12 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); - body = await BuildHtml(plexm, embym, plext, embyt, settings); + var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); + body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings); if (body.IsNullOrEmpty()) { return; @@ -298,7 +315,8 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, HashSet plexEpisodes, HashSet embyEp, NewsletterSettings settings) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, + HashSet plexEpisodes, HashSet embyEp, HashSet albums, NewsletterSettings settings) { var sb = new StringBuilder(); @@ -340,6 +358,24 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); } + + if (albums.Any() && !settings.DisableMusic) + { + sb.Append("

New Albums



"); + sb.Append( + ""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append("
"); + sb.Append(""); + sb.Append(""); + await ProcessAlbums(albums, sb); + sb.Append(""); + sb.Append("
"); + sb.Append("
"); + } + return sb.ToString(); } @@ -382,6 +418,40 @@ namespace Ombi.Schedule.Jobs.Ombi } } } + private async Task ProcessAlbums(HashSet albumsToSend, StringBuilder sb) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + int count = 0; + var ordered = albumsToSend.OrderByDescending(x => x.AddedAt); + foreach (var content in ordered) + { + var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri); + if (info == null) + { + continue; + } + try + { + CreateAlbumHtmlContent(sb, info); + count += 1; + } + catch (Exception e) + { + _log.LogError(e, "Error when Processing Lidarr Album {0}", info.title); + } + finally + { + EndLoopHtml(sb); + } + + if (count == 2) + { + count = 0; + sb.Append(""); + sb.Append(""); + } + } + } private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) { @@ -467,6 +537,41 @@ namespace Ombi.Schedule.Jobs.Ombi } } + private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info) + { + var cover = info.images + .FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url; + if (cover.IsNullOrEmpty()) + { + cover = info.remoteCover; + } + AddBackgroundInsideTable(sb, cover); + var disk = info.images + .FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url; + if (disk.IsNullOrEmpty()) + { + disk = info.remoteCover; + } + AddPosterInsideTable(sb, disk); + + AddMediaServerUrl(sb, string.Empty, string.Empty); + AddInfoTable(sb); + + var releaseDate = $"({info.releaseDate.Year})"; + + AddTitle(sb, string.Empty, $"{info.title} {releaseDate}"); + + var summary = info.artist?.artistName ?? string.Empty; + if (summary.Length > 280) + { + summary = summary.Remove(280); + summary = summary + "...

"; + } + AddParagraph(sb, summary); + + AddGenres(sb, $"Type: {info.albumType}"); + } + private async Task ProcessPlexTv(HashSet plexContent, StringBuilder sb) { var series = new List(); diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index e79f3182c..3f6416af5 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications { public bool DisableTv { get; set; } public bool DisableMovies { get; set; } + public bool DisableMusic { get; set; } public bool Enabled { get; set; } public List ExternalEmails { get; set; } = new List(); } diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs index d9ceab8a3..03099face 100644 --- a/src/Ombi.Store/Entities/LidarrAlbumCache.cs +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -13,6 +13,7 @@ namespace Ombi.Store.Entities public bool Monitored { get; set; } public string Title { get; set; } public decimal PercentOfTracks { get; set; } + public DateTime AddedAt { get; set; } [NotMapped] public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index 1ef091149..782d89e3f 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -11,18 +11,21 @@ namespace Ombi.Store.Entities public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID public int? EpisodeNumber { get; set; } public int? SeasonNumber { get; set; } + public string AlbumId { get; set; } public DateTime AddedAt { get; set; } } public enum RecentlyAddedType { Plex = 0, - Emby = 1 + Emby = 1, + Lidarr = 2 } public enum ContentType { Parent = 0, - Episode = 1 + Episode = 1, + Album = 2, } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html index 43819ad76..c89c2be0a 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -189,7 +189,7 @@ - + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index b23a73221..1581c9a35 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -68,13 +68,13 @@
-
+ + +
-
+ -->
@@ -93,11 +93,9 @@ | translate }}
- -
- + - \ No newline at end of file + + + + + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts index 0f9a373e2..9dac4aa8b 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AuthService } from "../../auth/auth.service"; -import { IRequestEngineResult } from "../../interfaces"; +import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult"; import { NotificationService, RequestService } from "../../services"; @@ -14,7 +14,15 @@ export class AlbumSearchComponent { @Input() public result: ISearchAlbumResult; public engineResult: IRequestEngineResult; - @Input() public defaultPoster: string; + @Input() public defaultPoster: string; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; @Output() public setSearch = new EventEmitter(); @@ -29,6 +37,14 @@ export class AlbumSearchComponent { this.setSearch.emit(artistId); } + public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) { + this.issueRequestId = req.id; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + public request(searchResult: ISearchAlbumResult) { searchResult.requested = true; searchResult.requestProcessing = true;