diff --git a/CHANGELOG.md b/CHANGELOG.md index 719fe8c9d..c317fbc17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,68 @@ ### **New Features** +- Added the Recently Added Newsletter! You are welcome. [tidusjar] + +- Added a new scrollbar to Ombi. [tidusjar] + +- Added the ability to automatically generate the API Key on startup if it does not exist #2070. [tidusjar] + +- Updated npm dependancies. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update ISSUE_TEMPLATE.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added recently added stuff. [Jamie] + +- Added the recently added engine with some basic methods. [Jamie] + - Added the ability to refresh out backend metadata (#2078) [Jamie] ### **Fixes** -- Fixed #2074 and #2079. [Jamie] +- Specific favicons for different platforms. [louis-lau] + +- MovieDbId was switched to string fron number so accomodated for change. [Anojh] + +- Removing duplicate functions. [Anojh Thayaparan] + +- Conflict resolving and adopting Jamie's new method. [Anojh] + +- Wrote new calls to just get poster and bg. [Anojh] + +- Fix for issue #1907, which is to add content poster and bg to issue details page. [Anojh] + +- Dynamic Background Animation. [Anojh] + +- Improved the message for #2037. [tidusjar] + +- Improved the way we use the notification variables, we have now split out the Username and Alias (Requested User is depricated but not removed) [tidusjar] + +- Removed redundant timers. [Anojh] + +- More optimizations by reducing requests. [Anojh] + +- Improved version. [Anojh] + +- Dynamic Background Animation. [Anojh] + +- Fixed #2055 and #1903. [Jamie] - Small changes to the auto updater, let's see how this works. [Jamie] +- Fixed build. [Jamie] + +- Fixed the update check for the master build. [Jamie] + +- Fixed build. [Jamie] + +- Fixed #2074 and #2079. [Jamie] + ## v3.0.3030 (2018-03-14) @@ -19,6 +73,10 @@ - Updated the .Net core dependancies #2072. [Jamie] +### **Fixes** + +- Delete Ombi.testdb. [Jamie] + ## v3.0.3020 (2018-03-13) diff --git a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs index 30f415aad..28eb066d4 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Store.Entities.Requests; @@ -9,7 +10,7 @@ namespace Ombi.Core.Engine.Interfaces { Task RemoveTvRequest(int requestId); - Task RequestTvShow(SearchTvShowViewModel tv); + Task RequestTvShow(TvRequestViewModel tv); Task DenyChildRequest(int requestId); Task> SearchTvRequest(string search); Task>>> SearchTvRequestTree(string search); diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 0b376386f..f4b0ee48c 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -54,7 +54,7 @@ namespace Ombi.Core.Engine { Result = false, Message = "There was an issue adding this movie!", - ErrorMessage = $"TheMovieDb didn't have any information for ID {model.TheMovieDbId}" + ErrorMessage = $"Please try again later" }; } var fullMovieName = diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index f8fc33e9b..aaa2d353d 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -43,13 +43,13 @@ namespace Ombi.Core.Engine private IAuditRepository Audit { get; } private readonly IRepository _requestLog; - public async Task RequestTvShow(SearchTvShowViewModel tv) + public async Task RequestTvShow(TvRequestViewModel tv) { var user = await GetUser(); var tvBuilder = new TvShowRequestBuilder(TvApi); (await tvBuilder - .GetShowInfo(tv.Id)) + .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) .CreateChild(tv, user.Id); @@ -78,9 +78,9 @@ namespace Ombi.Core.Engine } } - await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tv.Title}", Username); + await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username); - var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.Id); + var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId); if (existingRequest != null) { // Remove requests we already have, we just want new ones @@ -127,7 +127,7 @@ namespace Ombi.Core.Engine var newRequest = tvBuilder.CreateNewRequest(tv); return await AddRequest(newRequest.NewRequest); } - + public async Task> GetRequests(int count, int position) { var shouldHide = await HideFromOtherUsers(); diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 0d5d46c54..1f92536b8 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Ombi.Api.TvMaze; using Ombi.Api.TvMaze.Models; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Helpers; using Ombi.Store.Entities; @@ -23,7 +24,7 @@ namespace Ombi.Core.Helpers private ITvMazeApi TvApi { get; } public ChildRequests ChildRequest { get; set; } - public List TvRequests { get; protected set; } + public List TvRequests { get; protected set; } public string PosterPath { get; protected set; } public DateTime FirstAir { get; protected set; } public TvRequests NewRequest { get; protected set; } @@ -33,7 +34,7 @@ namespace Ombi.Core.Helpers { ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); - DateTime.TryParse(ShowInfo.premiered, out DateTime dt); + DateTime.TryParse(ShowInfo.premiered, out var dt); FirstAir = dt; @@ -43,37 +44,29 @@ namespace Ombi.Core.Helpers return this; } - public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, string userId) + public TvShowRequestBuilder CreateChild(TvRequestViewModel model, string userId) { ChildRequest = new ChildRequests { - Id = model.Id, + Id = model.TvDbId, RequestType = RequestType.TvShow, RequestedDate = DateTime.UtcNow, Approved = false, RequestedUserId = userId, SeasonRequests = new List(), - Title = model.Title, + Title = ShowInfo.name, SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard }; return this; } - public TvShowRequestBuilder CreateTvList(SearchTvShowViewModel tv) + public TvShowRequestBuilder CreateTvList(TvRequestViewModel tv) { - TvRequests = new List(); + TvRequests = new List(); // Only have the TV requests we actually requested and not everything - foreach (var season in tv.SeasonRequests) + foreach (var season in tv.Seasons) { - for (int i = season.Episodes.Count - 1; i >= 0; i--) - { - if (!season.Episodes[i].Requested) - { - season.Episodes.RemoveAt(i); // Remove the episode since it's not requested - } - } - if (season.Episodes.Any()) { TvRequests.Add(season); @@ -81,11 +74,10 @@ namespace Ombi.Core.Helpers } return this; - } - public async Task BuildEpisodes(SearchTvShowViewModel tv) + public async Task BuildEpisodes(TvRequestViewModel tv) { if (tv.RequestAll) { @@ -173,26 +165,68 @@ namespace Ombi.Core.Helpers else { // It's a custom request - ChildRequest.SeasonRequests = TvRequests; + var seasonRequests = new List(); + var episodes = await TvApi.EpisodeLookup(ShowInfo.id); + foreach (var ep in episodes) + { + var existingSeasonRequest = seasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season); + if (existingSeasonRequest != null) + { + var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season); + var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false; + if (requestedSeason != null && requestedEpisode) + { + // We already have this, let's just add the episodes to it + existingSeasonRequest.Episodes.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = FormatDate(ep.airdate), + Title = ep.name, + Url = ep.url, + }); + } + } + else + { + var newRequest = new SeasonRequests {SeasonNumber = ep.season}; + var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season); + var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false; + if (requestedSeason != null && requestedEpisode) + { + newRequest.Episodes.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = FormatDate(ep.airdate), + Title = ep.name, + Url = ep.url, + }); + seasonRequests.Add(newRequest); + } + } + } + + foreach (var s in seasonRequests) + { + ChildRequest.SeasonRequests.Add(s); + } } return this; } - public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv) + public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv) { NewRequest = new TvRequests { - Id = tv.Id, Overview = ShowInfo.summary.RemoveHtml(), PosterPath = PosterPath, Title = ShowInfo.name, ReleaseDate = FirstAir, Status = ShowInfo.status, ImdbId = ShowInfo.externals?.imdb ?? string.Empty, - TvDbId = tv.Id, + TvDbId = tv.TvDbId, ChildRequests = new List(), - TotalSeasons = tv.SeasonRequests.Count() + TotalSeasons = tv.Seasons.Count() }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs new file mode 100644 index 000000000..78f9edd6d --- /dev/null +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Ombi.Core.Models.Requests +{ + public class TvRequestViewModel + { + public bool RequestAll { get; set; } + public bool LatestSeason { get; set; } + public bool FirstSeason { get; set; } + public int TvDbId { get; set; } + public List Seasons { get; set; } = new List(); + } + + public class SeasonsViewModel + { + public int SeasonNumber { get; set; } + public List Episodes { get; set; } = new List(); + } + + public class EpisodesViewModel + { + public int EpisodeNumber { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 21a10aebb..464e3463b 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -151,7 +151,7 @@

-

Here is a list of Movies and TV Shows that have recently been added!

+

{@INTRO}

diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 8309ff045..8c18fba4c 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text; using Ombi.Helpers; using Ombi.Notifications.Models; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; namespace Ombi.Notifications { @@ -25,9 +28,9 @@ namespace Ombi.Notifications } ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = string.IsNullOrEmpty(req?.RequestedUser?.Alias) - ? req?.RequestedUser?.UserName - : req?.RequestedUser?.Alias; + RequestedUser = req?.RequestedUser?.UserName; + UserName = req?.RequestedUser?.UserName; + Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; Title = title; RequestedDate = req?.RequestedDate.ToString("D"); Type = req?.RequestType.ToString(); @@ -43,6 +46,8 @@ namespace Ombi.Notifications ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; RequestedUser = username.UserName; + UserName = username.UserName; + Alias = username.Alias.HasValue() ? username.Alias : username.UserName; } public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s) @@ -59,9 +64,9 @@ namespace Ombi.Notifications } ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = string.IsNullOrEmpty(req?.RequestedUser.Alias) - ? req?.RequestedUser.UserName - : req?.RequestedUser.Alias; + RequestedUser = req?.RequestedUser?.UserName; + UserName = req?.RequestedUser?.UserName; + Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; Title = title; RequestedDate = req?.RequestedDate.ToString("D"); Type = req?.RequestType.ToString(); @@ -71,6 +76,40 @@ namespace Ombi.Notifications $"https://image.tmdb.org/t/p/w300{req?.ParentRequest.PosterPath}" : req?.ParentRequest.PosterPath; AdditionalInformation = opts.AdditionalInformation; // DO Episode and Season Lists + + var episodes = req?.SeasonRequests?.SelectMany(x => x.Episodes) ?? new List(); + var seasons = req?.SeasonRequests?.OrderBy(x => x.SeasonNumber).ToList() ?? new List(); + var orderedEpisodes = episodes.OrderBy(x => x.EpisodeNumber).ToList(); + var epSb = new StringBuilder(); + var seasonSb = new StringBuilder(); + for (var i = 0; i < orderedEpisodes.Count; i++) + { + var ep = orderedEpisodes[i]; + if (i < orderedEpisodes.Count - 1) + { + epSb.Append($"{ep.EpisodeNumber},"); + } + else + { + epSb.Append($"{ep.EpisodeNumber}"); + } + } + + for (var i = 0; i < seasons.Count; i++) + { + var ep = seasons[i]; + if (i < seasons.Count - 1) + { + seasonSb.Append($"{ep.SeasonNumber},"); + } + else + { + seasonSb.Append($"{ep.SeasonNumber}"); + } + } + + EpisodesList = epSb.ToString(); + SeasonsList = seasonSb.ToString(); } public void Setup(OmbiUser user, CustomizationSettings s) @@ -88,13 +127,14 @@ namespace Ombi.Notifications IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty; IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; - RequestedUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; + UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; } // User Defined public string RequestedUser { get; set; } - public string UserName => RequestedUser; - public string IssueUser => RequestedUser; + public string UserName { get; set; } + public string IssueUser => UserName; + public string Alias { get; set; } public string Title { get; set; } public string RequestedDate { get; set; } @@ -144,6 +184,7 @@ namespace Ombi.Notifications {nameof(NewIssueComment),NewIssueComment}, {nameof(IssueUser),IssueUser}, {nameof(UserName),UserName}, + {nameof(Alias),Alias}, }; } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index c3fcbb9d7..42a66dfbe 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -76,8 +76,8 @@ namespace Ombi.Schedule.Jobs.Ombi var customization = await _customizationSettings.GetSettingsAsync(); // Get the Content - var plexContent = _plex.GetAll().Include(x => x.Episodes); - var embyContent = _emby.GetAll().Include(x => x.Episodes); + var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); + var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); var addedLog = _recentlyAddedLog.GetAll(); var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); @@ -90,24 +90,21 @@ namespace Ombi.Schedule.Jobs.Ombi var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id)); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id)); - var plexContentTvToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show && x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id))); - var embyContentTvToSend = embyContent.Where(x => x.Type == EmbyMediaType.Series && x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id))); - - var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend); - var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend); + var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking(); + var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking(); var body = string.Empty; if (test) { var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var plext = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show).OrderByDescending(x => x.AddedAt).Take(10); - var embyt = embyContent.Where(x => x.Type == EmbyMediaType.Series).OrderByDescending(x => x.AddedAt).Take(10); - body = await BuildHtml(plexm.Union(plext), embym.Union(embyt)); + var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); + var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); + body = await BuildHtml(plexm, embym, plext, embyt); } else { - body = await BuildHtml(plexContentToSend, embyContentToSend); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend); if (body.IsNullOrEmpty()) { return; @@ -137,7 +134,7 @@ namespace Ombi.Schedule.Jobs.Ombi var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); emailTasks.Add(_email.Send( - new NotificationMessage {Message = html, Subject = messageContent.Subject, To = user.Email}, + new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email }, emailSettings)); } @@ -145,28 +142,25 @@ namespace Ombi.Schedule.Jobs.Ombi var recentlyAddedLog = new HashSet(); foreach (var p in plexContentMoviesToSend) { - if (p.Type == PlexMediaTypeEntity.Movie) + recentlyAddedLog.Add(new RecentlyAddedLog { - recentlyAddedLog.Add(new RecentlyAddedLog - { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentId = p.Id - }); - } - else + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Parent, + ContentId = p.Id + }); + + } + + foreach (var p in plexEpisodesToSend) + { + recentlyAddedLog.Add(new RecentlyAddedLog { - // Add the episodes - foreach (var ep in p.Episodes) - { - recentlyAddedLog.Add(new RecentlyAddedLog - { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentId = ep.Id - }); - } - } + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Episode, + ContentId = p.Id + }); } foreach (var e in embyContentMoviesToSend) @@ -177,22 +171,21 @@ namespace Ombi.Schedule.Jobs.Ombi { AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, + ContentType = ContentType.Parent, ContentId = e.Id }); } - else + } + + foreach (var p in embyEpisodesToSend) + { + recentlyAddedLog.Add(new RecentlyAddedLog { - // Add the episodes - foreach (var ep in e.Episodes) - { - recentlyAddedLog.Add(new RecentlyAddedLog - { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentId = ep.Id - }); - } - } + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Emby, + ContentType = ContentType.Episode, + ContentId = p.Id + }); } await _recentlyAddedLog.AddRange(recentlyAddedLog); @@ -212,7 +205,7 @@ namespace Ombi.Schedule.Jobs.Ombi var email = new NewsletterTemplate(); var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); - + await _email.Send( new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email }, emailSettings); @@ -236,7 +229,7 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp) { var sb = new StringBuilder(); @@ -249,13 +242,11 @@ namespace Ombi.Schedule.Jobs.Ombi await ProcessEmbyMovies(embyMovies, sb); } - var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show); - var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series); - if (plexTv.Any() || embyTv.Any()) + if (plexEpisodes.Any() || embyEp.Any()) { sb.Append("

New Episodes:



"); - await ProcessPlexTv(plexTv, sb); - await ProcessEmbyMovies(embyTv, sb); + await ProcessPlexTv(plexEpisodes, sb); + await ProcessEmbyTv(embyEp, sb); } return sb.ToString(); @@ -288,22 +279,7 @@ namespace Ombi.Schedule.Jobs.Ombi } try { - AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); - - sb.Append(""); - TableData(sb); - - Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); - Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}"); - EndTag(sb, "a"); - - if (info.Genres.Any()) - { - AddParagraph(sb, - $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); - } - - AddParagraph(sb, info.Overview); + CreateMovieHtmlContent(sb, info); } catch (Exception e) { @@ -316,6 +292,7 @@ namespace Ombi.Schedule.Jobs.Ombi } } } + private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) { sb.Append( @@ -323,30 +300,21 @@ namespace Ombi.Schedule.Jobs.Ombi var ordered = embyContent.OrderByDescending(x => x.AddedAt); foreach (var content in ordered) { - int.TryParse(content.ProviderId, out var movieDbId); - var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); + var imdbId = content.ProviderId; + var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id); + var result = findResult.movie_results?.FirstOrDefault(); + if(result == null) + { + continue; + } + var info = await _movieApi.GetMovieInformationWithExtraInfo(result.id); if (info == null) { continue; } try { - AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}"); - - sb.Append(""); - TableData(sb); - - Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); - Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}"); - EndTag(sb, "a"); - - if (info.Genres.Any()) - { - AddParagraph(sb, - $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); - } - - AddParagraph(sb, info.Overview); + CreateMovieHtmlContent(sb, info); } catch (Exception e) { @@ -360,9 +328,57 @@ namespace Ombi.Schedule.Jobs.Ombi } } - private async Task ProcessPlexTv(IQueryable plexContent, StringBuilder sb) + private void CreateMovieHtmlContent(StringBuilder sb, MovieResponseDto info) { - var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); + AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.PosterPath}"); + + sb.Append(""); + TableData(sb); + + Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/"); + var releaseDate = string.Empty; + try + { + releaseDate = $"({DateTime.Parse(info.ReleaseDate).Year})"; + } + catch (Exception) + { + // Swallow, couldn't parse the date + } + Header(sb, 3, $"{info.Title} {releaseDate}"); + EndTag(sb, "a"); + + if (info.Genres.Any()) + { + AddParagraph(sb, + $"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}"); + } + + AddParagraph(sb, info.Overview); + } + + private async Task ProcessPlexTv(IQueryable plexContent, StringBuilder sb) + { + var series = new List(); + foreach (var plexEpisode in plexContent) + { + var alreadyAdded = series.FirstOrDefault(x => x.Key == plexEpisode.Series.Key); + if (alreadyAdded != null) + { + var episodeExists = alreadyAdded.Episodes.Any(x => x.Key == plexEpisode.Key); + if (!episodeExists) + { + alreadyAdded.Episodes.Add(plexEpisode); + } + } + else + { + plexEpisode.Series.Episodes = new List { plexEpisode }; + series.Add(plexEpisode.Series); + } + } + + var orderedTv = series.OrderByDescending(x => x.AddedAt); sb.Append( ""); foreach (var t in orderedTv) @@ -412,15 +428,15 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); sb.Append( "
"); - - var title = $"{t.Title} {t.ReleaseYear}"; + + var title = $"{t.Title} ({t.ReleaseYear})"; Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); Header(sb, 3, title); EndTag(sb, "a"); // Group by the season number - var results = t.Episodes?.GroupBy(p => p.SeasonNumber, + var results = t.Episodes.GroupBy(p => p.SeasonNumber, (key, g) => new { SeasonNumber = key, @@ -469,9 +485,26 @@ namespace Ombi.Schedule.Jobs.Ombi } - private async Task ProcessEmbyTv(IQueryable plexContent, StringBuilder sb) + private async Task ProcessEmbyTv(IQueryable embyContent, StringBuilder sb) { - var orderedTv = plexContent.OrderByDescending(x => x.AddedAt); + var series = new List(); + foreach (var episode in embyContent) + { + var alreadyAdded = series.FirstOrDefault(x => x.EmbyId == episode.Series.EmbyId); + if (alreadyAdded != null) + { + alreadyAdded.Episodes.Add(episode); + } + else + { + episode.Series.Episodes = new List + { + episode + }; + series.Add(episode.Series); + } + } + var orderedTv = series.OrderByDescending(x => x.AddedAt); sb.Append( ""); foreach (var t in orderedTv) diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 31b879d11..e4c9be516 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -119,7 +119,10 @@ namespace Ombi.Store.Context var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter); if (!roles.Any()) { - Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter)); + Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter) + { + NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper() + }); } //Check if templates exist var templates = NotificationTemplates.ToList(); @@ -143,7 +146,7 @@ namespace Ombi.Store.Context notificationToAdd = new NotificationTemplates { NotificationType = notificationType, - Message = "Hello! The user '{RequestedUser}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}", + Message = "Hello! The user '{UserName}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}", Subject = "{ApplicationName}: New {Type} request for {Title}!", Agent = agent, Enabled = true, @@ -153,7 +156,7 @@ namespace Ombi.Store.Context notificationToAdd = new NotificationTemplates { NotificationType = notificationType, - Message = "Hello! The user '{IssueUser}' has reported a new issue for the title {Title}!
{IssueCategory} - {IssueSubject} : {IssueDescription}", + Message = "Hello! The user '{UserName}' has reported a new issue for the title {Title}!
{IssueCategory} - {IssueSubject} : {IssueDescription}", Subject = "{ApplicationName}: New issue for {Title}!", Agent = agent, Enabled = true, @@ -163,7 +166,7 @@ namespace Ombi.Store.Context notificationToAdd = new NotificationTemplates { NotificationType = notificationType, - Message = "Hello! You requested {Title} on {ApplicationName}! This is now available! :)", + Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)", Subject = "{ApplicationName}: {Title} is now available!", Agent = agent, Enabled = true, @@ -207,7 +210,7 @@ namespace Ombi.Store.Context notificationToAdd = new NotificationTemplates { NotificationType = notificationType, - Message = "Hello {IssueUser} Your issue for {Title} has now been resolved.", + Message = "Hello {UserName} Your issue for {Title} has now been resolved.", Subject = "{ApplicationName}: Issue has been resolved for {Title}!", Agent = agent, Enabled = true, diff --git a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs index 496f1988d..521cf5b94 100644 --- a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs +++ b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs @@ -10,7 +10,7 @@ namespace Ombi.Store.Repository.Requests public class SeasonRequests : Entity { public int SeasonNumber { get; set; } - public List Episodes { get; set; } + public List Episodes { get; set; } = new List(); public int ChildRequestId { get; set; } [ForeignKey(nameof(ChildRequestId))] diff --git a/src/Ombi/ClientApp/app/animations/fadeinout.ts b/src/Ombi/ClientApp/app/animations/fadeinout.ts new file mode 100644 index 000000000..8ecf15a15 --- /dev/null +++ b/src/Ombi/ClientApp/app/animations/fadeinout.ts @@ -0,0 +1,12 @@ +import { animate, style, transition, trigger } from "@angular/animations"; +import { AnimationEntryMetadata } from "@angular/core"; + +export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [ + transition(":enter", [ // :enter is alias to 'void => *' + style({ opacity: 0 }), + animate(1000, style({ opacity: 1 })), + ]), + transition(":leave", [ // :leave is alias to '* => void' + animate(1000, style({ opacity: 0 })), + ]), +]); diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts index 5ed9567f5..f0afa76b2 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts @@ -30,3 +30,20 @@ export interface ISearchTvResult { firstSeason: boolean; latestSeason: boolean; } + +export interface ITvRequestViewModel { + requestAll: boolean; + firstSeason: boolean; + latestSeason: boolean; + tvDbId: number; + seasons: ISeasonsViewModel[]; +} + +export interface ISeasonsViewModel { + seasonNumber: number; + episodes: IEpisodesViewModel[]; +} + +export interface IEpisodesViewModel { + episodeNumber: number; +} diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.html b/src/Ombi/ClientApp/app/issues/issueDetails.component.html index 1c1e0f5c4..bad4885e7 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.html +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.html @@ -1,13 +1,16 @@
+
+

{{issue.title}}

- {{IssueStatus[issue.status]}} - {{issue.issueCategory.value}} - -

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}

-

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}

+ poster + {{IssueStatus[issue.status]}} + {{issue.issueCategory.value}} + +

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}

+

{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}

{{'Issues.Subject' | translate}}: {{issue.subject}}

-
+
@@ -26,7 +29,8 @@

- {{'Issues.Comments' | translate}}

+ {{'Issues.Comments' | translate}} +
@@ -51,8 +55,7 @@