diff --git a/.azuredevops/pipelines/templates/variables.yml b/.azuredevops/pipelines/templates/variables.yml index 205e68eaa..e90ecc049 100644 --- a/.azuredevops/pipelines/templates/variables.yml +++ b/.azuredevops/pipelines/templates/variables.yml @@ -27,4 +27,4 @@ variables: value: "4.0.$(Build.BuildId)" - name: isMain - value: $[or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))] \ No newline at end of file + value: $[or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))] diff --git a/README.md b/README.md index 2ff3e0ccd..60f886a72 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Search the existing requests to see if your suggestion has already been submitte ___ Get it on Google Play
-_**Note:** There is no longer an iOS app due to complications outside of our control._ - +Get it on the App Store +
# Features Here are some of the features Ombi has: diff --git a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs index 74d5c75ea..6eb2f1c5a 100644 --- a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs +++ b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs @@ -1,39 +1,122 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Net.Mime; namespace Ombi.Api.Radarr.Models -{ +{ public class MovieResponse { public string title { get; set; } + public string originalTitle { get; set; } + public Alternatetitle[] alternateTitles { get; set; } + public int secondaryYearSourceId { get; set; } public string sortTitle { get; set; } - public double sizeOnDisk { get; set; } + public long sizeOnDisk { get; set; } public string status { get; set; } public string overview { get; set; } - public string inCinemas { get; set; } - public string physicalRelease { get; set; } - public List images { get; set; } + public DateTime inCinemas { get; set; } + public DateTime physicalRelease { get; set; } + public DateTime digitalRelease { get; set; } + public Image[] images { get; set; } public string website { get; set; } - public bool downloaded { get; set; } public int year { get; set; } public bool hasFile { get; set; } public string youTubeTrailerId { get; set; } public string studio { get; set; } public string path { get; set; } - public int profileId { get; set; } - public string minimumAvailability { get; set; } + public int qualityProfileId { get; set; } public bool monitored { get; set; } + public string minimumAvailability { get; set; } + public bool isAvailable { get; set; } + public string folderName { get; set; } public int runtime { get; set; } - public string lastInfoSync { get; set; } public string cleanTitle { get; set; } public string imdbId { get; set; } public int tmdbId { get; set; } public string titleSlug { get; set; } - public List genres { get; set; } - public List tags { get; set; } - public string added { get; set; } + public string certification { get; set; } + public string[] genres { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } public Ratings ratings { get; set; } - //public List alternativeTitles { get; set; } - public int qualityProfileId { get; set; } + public Moviefile movieFile { get; set; } + public Collection collection { get; set; } + public int id { get; set; } + } + + + public class Moviefile + { + public int movieId { get; set; } + public string relativePath { get; set; } + public string path { get; set; } + public long size { get; set; } + public DateTime dateAdded { get; set; } + public string sceneName { get; set; } + public int indexerFlags { get; set; } + public V3.Quality quality { get; set; } + public Mediainfo mediaInfo { get; set; } + public string originalFilePath { get; set; } + public bool qualityCutoffNotMet { get; set; } + public Language[] languages { get; set; } + public string releaseGroup { get; set; } + public string edition { get; set; } + public int id { get; set; } + } + + public class Revision + { + public int version { get; set; } + public int real { get; set; } + public bool isRepack { get; set; } + } + + public class Mediainfo + { + public string audioAdditionalFeatures { get; set; } + public int audioBitrate { get; set; } + public float audioChannels { get; set; } + public string audioCodec { get; set; } + public string audioLanguages { get; set; } + public int audioStreamCount { get; set; } + public int videoBitDepth { get; set; } + public int videoBitrate { get; set; } + public string videoCodec { get; set; } + public float videoFps { get; set; } + public string resolution { get; set; } + public string runTime { get; set; } + public string scanType { get; set; } + public string subtitles { get; set; } + } + + public class Language + { + public int id { get; set; } + public string name { get; set; } + } + + public class Collection + { + public string name { get; set; } + public int tmdbId { get; set; } + public object[] images { get; set; } + } + + public class Alternatetitle + { + public string sourceType { get; set; } + public int movieId { get; set; } + public string title { get; set; } + public int sourceId { get; set; } + public int votes { get; set; } + public int voteCount { get; set; } + public Language1 language { get; set; } + public int id { get; set; } + } + + public class Language1 + { public int id { get; set; } + public string name { get; set; } } -} +} \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs b/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs index b56049c9a..09e985f43 100644 --- a/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs +++ b/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs @@ -28,5 +28,6 @@ namespace Ombi.Api.Radarr.Models public string titleSlug { get; set; } public int year { get; set; } public string minimumAvailability { get; set; } + public long sizeOnDisk { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index b461ccda8..e1879bba3 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -82,7 +82,8 @@ namespace Ombi.Api.Radarr titleSlug = title + year, monitored = true, year = year, - minimumAvailability = minimumAvailability + minimumAvailability = minimumAvailability, + sizeOnDisk = 0 }; if (searchNow) diff --git a/src/Ombi.Api.Radarr/RadarrV3Api.cs b/src/Ombi.Api.Radarr/RadarrV3Api.cs index dd1d0b279..9e7e0f4c2 100644 --- a/src/Ombi.Api.Radarr/RadarrV3Api.cs +++ b/src/Ombi.Api.Radarr/RadarrV3Api.cs @@ -41,7 +41,7 @@ namespace Ombi.Api.Radarr public async Task SystemStatus(string apiKey, string baseUrl) { - var request = new Request("/api/v3/status", baseUrl, HttpMethod.Get); + var request = new Request("/api/v3/system/status", baseUrl, HttpMethod.Get); AddHeaders(request, apiKey); return await Api.Request(request); @@ -65,7 +65,7 @@ namespace Ombi.Api.Radarr public async Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl) { - var request = new Request($"/api/v3/movie/", baseUrl, HttpMethod.Put); + var request = new Request($"/api/v3/movie/{movie.id}", baseUrl, HttpMethod.Put); AddHeaders(request, apiKey); request.AddJsonBody(movie); @@ -85,7 +85,8 @@ namespace Ombi.Api.Radarr titleSlug = title + year, monitored = true, year = year, - minimumAvailability = minimumAvailability + minimumAvailability = minimumAvailability, + sizeOnDisk = 0 }; if (searchNow) diff --git a/src/Ombi.Api.Webhook/WebhookApi.cs b/src/Ombi.Api.Webhook/WebhookApi.cs index 8b6b35ca0..f2faaa18b 100644 --- a/src/Ombi.Api.Webhook/WebhookApi.cs +++ b/src/Ombi.Api.Webhook/WebhookApi.cs @@ -19,7 +19,7 @@ namespace Ombi.Api.Webhook public async Task PushAsync(string baseUrl, string accessToken, IDictionary parameters) { - var request = new Request("/", baseUrl, HttpMethod.Post); + var request = new Request("", baseUrl, HttpMethod.Post); if (!string.IsNullOrWhiteSpace(accessToken)) { diff --git a/src/Ombi.Core.Tests/Rule/Request/ExistingPlexRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/ExistingPlexRequestRuleTests.cs new file mode 100644 index 000000000..f5a362303 --- /dev/null +++ b/src/Ombi.Core.Tests/Rule/Request/ExistingPlexRequestRuleTests.cs @@ -0,0 +1,209 @@ +using MockQueryable.Moq; +using Moq; +using NUnit.Framework; +using Ombi.Core.Rule.Rules.Request; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ombi.Core.Tests.Rule.Request +{ + [TestFixture] + public class ExistingPlexRequestRuleTests + { + private ExistingPlexRequestRule Rule; + private Mock PlexContentRepo; + + [SetUp] + public void SetUp() + { + PlexContentRepo = new Mock(); + Rule = new ExistingPlexRequestRule(PlexContentRepo.Object); + } + + [Test] + public async Task RequestShow_DoesNotExistAtAll_IsSuccessful() + { + PlexContentRepo.Setup(x => x.GetAll()).Returns(new List().AsQueryable().BuildMock().Object); + var req = new ChildRequests + { + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + } + }, + SeasonNumber = 1 + } + } + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.True); + } + + [Test] + public async Task RequestShow_AllEpisodesAreaRequested_IsNotSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 2, + }, + }, + SeasonNumber = 1 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.False); + } + + + [Test] + public async Task RequestShow_SomeEpisodesAreaRequested_IsSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + RequestType = RequestType.TvShow, + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 2, + EpisodeNumber = 2, + }, + new EpisodeRequests + { + Id = 3, + EpisodeNumber = 3, + }, + }, + SeasonNumber = 1 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.True); + + var episodes = req.SeasonRequests.SelectMany(x => x.Episodes); + Assert.That(episodes.Count() == 1, "We didn't remove the episodes that have already been requested!"); + Assert.That(episodes.First().EpisodeNumber == 3, "We removed the wrong episode"); + } + + [Test] + public async Task RequestShow_NewSeasonRequest_IsSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + RequestType = RequestType.TvShow, + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 2, + EpisodeNumber = 2, + }, + new EpisodeRequests + { + Id = 3, + EpisodeNumber = 3, + }, + }, + SeasonNumber = 2 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + Assert.That(result.Success, Is.True); + } + + private void SetupMockData() + { + var childRequests = new List + { + new PlexServerContent + { + Type = PlexMediaTypeEntity.Show, + TheMovieDbId = "1", + Title = "Test", + ReleaseYear = "2001", + Episodes = new List + { + new PlexEpisode + { + EpisodeNumber = 1, + Id = 1, + SeasonNumber = 1, + }, + new PlexEpisode + { + EpisodeNumber = 2, + Id = 2, + SeasonNumber = 1, + }, + } + } + }; + PlexContentRepo.Setup(x => x.GetAll()).Returns(childRequests.AsQueryable().BuildMock().Object); + } + } +} diff --git a/src/Ombi.Core.Tests/Rule/Request/ExistingTvRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/ExistingTvRequestRuleTests.cs new file mode 100644 index 000000000..d5ca903bf --- /dev/null +++ b/src/Ombi.Core.Tests/Rule/Request/ExistingTvRequestRuleTests.cs @@ -0,0 +1,215 @@ +using MockQueryable.Moq; +using Moq; +using NUnit.Framework; +using Ombi.Core.Rule.Rules.Request; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ombi.Core.Tests.Rule.Request +{ + [TestFixture] + public class ExistingTvRequestRuleTests + { + private ExistingTvRequestRule Rule; + private Mock TvRequestRepo; + + [SetUp] + public void SetUp() + { + TvRequestRepo = new Mock(); + Rule = new ExistingTvRequestRule(TvRequestRepo.Object); + } + + [Test] + public async Task RequestShow_DoesNotExistAtAll_IsSuccessful() + { + TvRequestRepo.Setup(x => x.GetChild()).Returns(new List().AsQueryable().BuildMock().Object); + var req = new ChildRequests + { + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + } + }, + SeasonNumber = 1 + } + } + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.True); + } + + [Test] + public async Task RequestShow_AllEpisodesAreaRequested_IsNotSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 2, + }, + }, + SeasonNumber = 1 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.False); + } + + + [Test] + public async Task RequestShow_SomeEpisodesAreaRequested_IsSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + RequestType = RequestType.TvShow, + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 2, + EpisodeNumber = 2, + }, + new EpisodeRequests + { + Id = 3, + EpisodeNumber = 3, + }, + }, + SeasonNumber = 1 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + + Assert.That(result.Success, Is.True); + + var episodes = req.SeasonRequests.SelectMany(x => x.Episodes); + Assert.That(episodes.Count() == 1, "We didn't remove the episodes that have already been requested!"); + Assert.That(episodes.First().EpisodeNumber == 3, "We removed the wrong episode"); + } + + [Test] + public async Task RequestShow_NewSeasonRequest_IsSuccessful() + { + SetupMockData(); + + var req = new ChildRequests + { + RequestType = RequestType.TvShow, + SeasonRequests = new List + { + new SeasonRequests + { + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 2, + EpisodeNumber = 2, + }, + new EpisodeRequests + { + Id = 3, + EpisodeNumber = 3, + }, + }, + SeasonNumber = 2 + } + }, + Id = 1, + }; + var result = await Rule.Execute(req); + + Assert.That(result.Success, Is.True); + } + + private void SetupMockData() + { + var childRequests = new List + { + new ChildRequests + { + ParentRequest = new TvRequests + { + Id = 1, + ExternalProviderId = 1, + }, + SeasonRequests = new List + { + new SeasonRequests + { + Id = 1, + SeasonNumber = 1, + Episodes = new List + { + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 1, + }, + new EpisodeRequests + { + Id = 1, + EpisodeNumber = 2, + } + } + } + } + } + }; + TvRequestRepo.Setup(x => x.GetChild()).Returns(childRequests.AsQueryable().BuildMock().Object); + } + } +} diff --git a/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs index 27dbee614..7bf84d05f 100644 --- a/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs @@ -30,7 +30,7 @@ namespace Ombi.Core.Tests.Rule.Search public async Task Should_Not_Be_Monitored_Or_Available() { var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -49,7 +49,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -71,7 +71,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -93,7 +93,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -114,7 +114,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); diff --git a/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs index c17400acb..e13d8d1cd 100644 --- a/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs @@ -29,7 +29,7 @@ namespace Ombi.Core.Tests.Rule.Search public async Task Should_Not_Be_Monitored() { var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Monitored); @@ -46,7 +46,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.True(request.Monitored); @@ -64,7 +64,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.True(request.Monitored); diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index d2e18d2b2..deef9ebea 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -48,6 +48,8 @@ namespace Ombi.Core.Engine protected readonly ISettingsService OmbiSettings; protected readonly IRepository _subscriptionRepository; + private bool _demo = DemoSingleton.Instance.Demo; + protected async Task> GetMovieRequests() { var now = DateTime.Now.Ticks; @@ -193,6 +195,23 @@ namespace Ombi.Core.Engine return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync()); } + protected bool DemoCheck(string title) + { + if (!title.HasValue()) + { + return false; + } + if (_demo) + { + if (ExcludedDemo.ExcludedContent.Any(x => title.Contains(x, System.Globalization.CompareOptions.OrdinalIgnoreCase))) + { + return true; + } + return false; + } + return false; + } + public class HideResult { public bool Hide { get; set; } diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 6fd22bced..68a4335cb 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -59,9 +59,9 @@ namespace Ombi.Core.Engine.Interfaces var ruleResults = await Rules.StartSearchRules(model); return ruleResults; } - public async Task RunSpecificRule(object model, SpecificRules rule) + public async Task RunSpecificRule(object model, SpecificRules rule, string requestOnBehalf) { - var ruleResults = await Rules.StartSpecificRules(model, rule); + var ruleResults = await Rules.StartSpecificRules(model, rule, requestOnBehalf); return ruleResults; } } diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 64c76b18f..dfcd1b1da 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Ombi.Core.Models.Requests; using Ombi.Core.Models.UI; @@ -11,6 +12,7 @@ namespace Ombi.Core.Engine.Interfaces Task RequestMovie(MovieRequestViewModel model); Task> SearchMovieRequest(string search); + Task RequestCollection(int collectionId, CancellationToken cancellationToken); Task RemoveMovieRequest(int requestId); Task RemoveAllMovieRequests(); diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index c8b7746f0..f4eeb6fc3 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Ombi.Core.Models; using Ombi.Core.Models.Requests; @@ -24,5 +25,6 @@ namespace Ombi.Core.Engine.Interfaces Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); Task GetRemainingRequests(OmbiUser user = null); + Task ReProcessRequest(int requestId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 00a97f766..269961c2f 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -21,6 +21,7 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; +using System.Threading; namespace Ombi.Core.Engine { @@ -70,7 +71,7 @@ namespace Ombi.Core.Engine var canRequestOnBehalf = model.RequestOnBehalf.HasValue(); var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); - if (model.RequestOnBehalf.HasValue() && !isAdmin) + if (canRequestOnBehalf && !isAdmin) { return new RequestEngineResult { @@ -549,12 +550,41 @@ namespace Ombi.Core.Engine request.Denied = false; await MovieRepository.Update(request); - var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty); if (canNotify.Success) { await NotificationHelper.Notify(request, NotificationType.RequestApproved); } + return await ProcessSendingMovie(request); + } + + public async Task RequestCollection(int collectionId, CancellationToken cancellationToken) + { + var langCode = await DefaultLanguageCode(null); + var collections = await Cache.GetOrAdd($"GetCollection{collectionId}{langCode}", + async () => await MovieApi.GetCollection(langCode, collectionId, cancellationToken), DateTime.Now.AddDays(1), cancellationToken); + + var results = new List(); + foreach (var collection in collections.parts) + { + results.Add(await RequestMovie(new MovieRequestViewModel + { + TheMovieDbId = collection.id + })); + } + + + if (results.All(x => x.IsError)) + { + new RequestEngineResult { Result = false, ErrorMessage = $"The whole collection {collections.name} Is already monitored or requested!" }; + } + + return new RequestEngineResult { Result = true, Message = $"The collection {collections.name} has been successfully added!", RequestId = results.FirstOrDefault().RequestId}; + } + + private async Task ProcessSendingMovie(MovieRequests request) + { if (request.Approved) { var result = await Sender.Send(request); @@ -634,6 +664,21 @@ namespace Ombi.Core.Engine return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); } + public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + { + var request = await MovieRepository.Find(requestId); + if (request == null) + { + return new RequestEngineResult + { + Result = false, + ErrorMessage = "Request does not exist" + }; + } + + return await ProcessSendingMovie(request); + } + public async Task MarkUnavailable(int modelId) { var request = await MovieRepository.Find(modelId); @@ -682,7 +727,7 @@ namespace Ombi.Core.Engine { await MovieRepository.Add(model); - var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf); if (result.Success) { await NotificationHelper.NewRequest(model); diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index e473c1fb1..032b2ae7d 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -362,7 +362,7 @@ namespace Ombi.Core.Engine await MusicRepository.Update(request); - var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty); if (canNotify.Success) { await NotificationHelper.Notify(request, NotificationType.RequestApproved); @@ -506,7 +506,7 @@ namespace Ombi.Core.Engine { await MusicRepository.Add(model); - var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, string.Empty); if (result.Success) { await NotificationHelper.NewRequest(model); diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index a9af03ecf..89bae7069 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -151,7 +151,7 @@ namespace Ombi.Core.Engine } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist, string.Empty); return vm; } @@ -190,7 +190,7 @@ namespace Ombi.Core.Engine vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl(); - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); @@ -230,7 +230,7 @@ namespace Ombi.Core.Engine vm.Cover = a.remoteCover; } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); @@ -258,7 +258,7 @@ namespace Ombi.Core.Engine vm.Cover = fullAlbum.remoteCover; } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index e332dde75..531fa161d 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -25,19 +25,22 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; +using System.Threading; +using Microsoft.Extensions.Logging; namespace Ombi.Core.Engine { public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine { public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, - INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, + INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, ILogger logger, ITvSender sender, IRepository rl, ISettingsService settings, ICacheService cache, IRepository sub) : base(user, requestService, rule, manager, cache, settings, sub) { TvApi = tvApi; MovieDbApi = movApi; NotificationHelper = helper; + _logger = logger; TvSender = sender; _requestLog = rl; } @@ -46,6 +49,8 @@ namespace Ombi.Core.Engine private ITvMazeApi TvApi { get; } private IMovieDbApi MovieDbApi { get; } private ITvSender TvSender { get; } + + private readonly ILogger _logger; private readonly IRepository _requestLog; public async Task RequestTvShow(TvRequestViewModel tv) @@ -68,7 +73,7 @@ namespace Ombi.Core.Engine } } - var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi); + var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi, _logger); (await tvBuilder .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) @@ -896,9 +901,25 @@ namespace Ombi.Core.Engine } + public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + { + var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId, cancellationToken); + if (request == null) + { + return new RequestEngineResult + { + Result = false, + ErrorMessage = "Request does not exist" + }; + } + + return await ProcessSendingShow(request); + } + + private async Task AfterRequest(ChildRequests model, string requestOnBehalf) { - var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf); if (sendRuleResult.Success) { await NotificationHelper.NewRequest(model); @@ -913,6 +934,11 @@ namespace Ombi.Core.Engine EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), }); + return await ProcessSendingShow(model); + } + + private async Task ProcessSendingShow(ChildRequests model) + { if (model.Approved) { // Autosend diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index f131662be..f3023c848 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -315,6 +315,12 @@ namespace Ombi.Core.Engine.V2 foreach (var movie in movies) { var result = await ProcessSingleMovie(movie); + + if (DemoCheck(result.Title)) + { + continue; + } + if (settings.HideAvailableFromDiscover && result.Available) { continue; diff --git a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs index b24f5a42c..fc2595676 100644 --- a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs +++ b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs @@ -35,6 +35,8 @@ namespace Ombi.Core.Engine.V2 private readonly ISettingsService _lidarrSettings; private readonly IMusicBrainzApi _musicApi; + private bool _demo = DemoSingleton.Instance.Demo; + public async Task> MultiSearch(string searchTerm, MultiSearchFilter filter, CancellationToken cancellationToken) { @@ -60,6 +62,12 @@ namespace Ombi.Core.Engine.V2 foreach (var multiSearch in movieDbData) { + + if (DemoCheck(multiSearch.title) || DemoCheck(multiSearch.name)) + { + continue; + } + var result = new MultiSearchResult { MediaType = multiSearch.media_type, diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs index 9d97285df..865693244 100644 --- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs @@ -50,19 +50,36 @@ namespace Ombi.Core.Engine.V2 public async Task GetShowByRequest(int requestId, CancellationToken token) { var request = await RequestService.TvRequestService.Get().FirstOrDefaultAsync(x => x.Id == requestId); - return await GetShowInformation(request.ExternalProviderId.ToString(), token); // TODO + return await GetShowInformation(request.ExternalProviderId.ToString(), token); } public async Task GetShowInformation(string tvdbid, CancellationToken token) { - var show = await Cache.GetOrAdd(nameof(GetShowInformation) + tvdbid, - async () => await _movieApi.GetTVInfo(tvdbid), DateTime.Now.AddHours(12)); + var langCode = await DefaultLanguageCode(null); + var show = await Cache.GetOrAdd(nameof(GetShowInformation) + langCode + tvdbid, + async () => await _movieApi.GetTVInfo(tvdbid, langCode), DateTime.Now.AddHours(12)); if (show == null || show.name == null) { // We don't have enough information return null; } + if (!show.Images?.Posters?.Any() ?? false && !string.Equals(langCode, "en", StringComparison.OrdinalIgnoreCase)) + { + // There's no regional assets for this, so + // lookup the en-us version to get them + var enShow = await Cache.GetOrAdd(nameof(GetShowInformation) + "en" + tvdbid, + async () => await _movieApi.GetTVInfo(tvdbid, "en"), DateTime.Now.AddHours(12)); + + // For some of the more obsecure cases + if (!show.overview.HasValue()) + { + show.overview = enShow.overview; + } + + show.Images = enShow.Images; + } + var mapped = _mapper.Map(show); @@ -154,6 +171,10 @@ namespace Ombi.Core.Engine.V2 foreach (var tvMazeSearch in items) { + if (DemoCheck(tvMazeSearch.Title)) + { + continue; + } if (settings.HideAvailableFromDiscover) { // To hide, we need to know if it's fully available, the only way to do this is to lookup it's episodes to check if we have every episode diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 39667eaca..fc66d197d 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -12,16 +12,19 @@ using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; +using Microsoft.Extensions.Logging; namespace Ombi.Core.Helpers { public class TvShowRequestBuilder { + private readonly ILogger _logger; - public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi) + public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi, ILogger logger) { TvApi = tvApi; MovieDbApi = movApi; + _logger = logger; } private ITvMazeApi TvApi { get; } @@ -45,6 +48,7 @@ namespace Ombi.Core.Helpers { if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase)) { + _logger.LogInformation($"Found matching MovieDb entry for show name {ShowInfo.name}"); TheMovieDbRecord = result; var showIds = await MovieDbApi.GetTvExternals(result.Id); ShowInfo.externals.imdb = showIds.imdb_id; @@ -237,18 +241,19 @@ namespace Ombi.Core.Helpers public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv) { + _logger.LogInformation($"Building Request for {ShowInfo.name} with Provider ID {TheMovieDbRecord?.Id ?? 0}"); NewRequest = new TvRequests { Overview = ShowInfo.summary.RemoveHtml(), PosterPath = PosterPath, Title = ShowInfo.name, ReleaseDate = FirstAir, - ExternalProviderId = TheMovieDbRecord.Id, + ExternalProviderId = TheMovieDbRecord?.Id ?? 0, Status = ShowInfo.status, ImdbId = ShowInfo.externals?.imdb ?? string.Empty, TvDbId = tv.TvDbId, ChildRequests = new List(), - TotalSeasons = tv.Seasons.Count(), + TotalSeasons = tv.Seasons?.Count ?? 0, Background = BackdropPath }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs index 1a77be97a..7a45e9f09 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs @@ -33,6 +33,14 @@ namespace Ombi.Core.Helpers public async Task GetShowInfo(int id) { TheMovieDbRecord = await MovieDbApi.GetTVInfo(id.ToString()); + + // Remove 'Specials Season' + var firstSeason = TheMovieDbRecord.seasons.OrderBy(x => x.season_number).FirstOrDefault(); + if (firstSeason?.season_number == 0) + { + TheMovieDbRecord.seasons.Remove(firstSeason); + } + BackdropPath = TheMovieDbRecord.Images?.Backdrops?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).FirstOrDefault()?.FilePath; ; DateTime.TryParse(TheMovieDbRecord.first_air_date, out var dt); @@ -149,6 +157,10 @@ namespace Ombi.Core.Helpers else if (tv.FirstSeason) { var first = allEpisodes.OrderBy(x => x.season_number).FirstOrDefault(); + if (first.season_number == 0) + { + first = allEpisodes.OrderBy(x => x.season_number).Skip(1).FirstOrDefault(); + } var episodesRequests = new List(); foreach (var ep in allEpisodes) { diff --git a/src/Ombi.Core/Models/Search/V2/MovieCollectionsViewModel.cs b/src/Ombi.Core/Models/Search/V2/MovieCollectionsViewModel.cs index 47870c18b..0cecd83e5 100644 --- a/src/Ombi.Core/Models/Search/V2/MovieCollectionsViewModel.cs +++ b/src/Ombi.Core/Models/Search/V2/MovieCollectionsViewModel.cs @@ -16,7 +16,7 @@ namespace Ombi.Core.Models.Search.V2 public string Overview { get; set; } public string PosterPath { get; set; } public string Title { get; set; } - public DateTime ReleaseDate { get; set; } + public DateTime? ReleaseDate { get; set; } public override RequestType Type => RequestType.Movie; diff --git a/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs b/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs index c32342146..5b438124e 100644 --- a/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs +++ b/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs @@ -9,6 +9,6 @@ namespace Ombi.Core.Rule.Interfaces { Task> StartRequestRules(BaseRequest obj); Task> StartSearchRules(SearchViewModel obj); - Task StartSpecificRules(object obj, SpecificRules selectedRule); + Task StartSpecificRules(object obj, SpecificRules selectedRule, string requestOnBehalf); } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs b/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs index 065b2f990..f76464888 100644 --- a/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs +++ b/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs @@ -5,7 +5,7 @@ namespace Ombi.Core.Rule.Interfaces { public interface ISpecificRule where T : new() { - Task Execute(T obj); + Task Execute(T obj, string requestOnBehalf); SpecificRules Rule { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/RuleEvaluator.cs b/src/Ombi.Core/Rule/RuleEvaluator.cs index a0c272c00..712d6031e 100644 --- a/src/Ombi.Core/Rule/RuleEvaluator.cs +++ b/src/Ombi.Core/Rule/RuleEvaluator.cs @@ -58,13 +58,13 @@ namespace Ombi.Core.Rule return results; } - public async Task StartSpecificRules(object obj, SpecificRules selectedRule) + public async Task StartSpecificRules(object obj, SpecificRules selectedRule, string requestOnBehalf) { foreach (var rule in SpecificRules) { if (selectedRule == rule.Rule) { - var result = await rule.Execute(obj); + var result = await rule.Execute(obj, requestOnBehalf); return result; } } diff --git a/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs index 927ebf1be..10ab93bed 100644 --- a/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs @@ -7,6 +7,7 @@ using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; namespace Ombi.Core.Rule.Rules.Request { @@ -60,6 +61,7 @@ namespace Ombi.Core.Rule.Rules.Request { foreach (var season in child.SeasonRequests) { + var episodesToRemove = new List(); var currentSeasonRequest = content.Episodes.Where(x => x.SeasonNumber == season.SeasonNumber).ToList(); if (!currentSeasonRequest.Any()) @@ -68,12 +70,24 @@ namespace Ombi.Core.Rule.Rules.Request } foreach (var e in season.Episodes) { - var hasEpisode = currentSeasonRequest.Any(x => x.EpisodeNumber == e.EpisodeNumber); - if (hasEpisode) + var existingEpRequest = currentSeasonRequest.FirstOrDefault(x => x.EpisodeNumber == e.EpisodeNumber); + if (existingEpRequest != null) { - return Fail($"We already have episodes requested from series {child.Title}"); + episodesToRemove.Add(e); } } + + episodesToRemove.ForEach(x => + { + season.Episodes.Remove(x); + }); + } + + var anyEpisodes = child.SeasonRequests.SelectMany(x => x.Episodes).Any(); + + if (!anyEpisodes) + { + return Fail($"We already have episodes requested from series {child.Title}"); } return Success(); diff --git a/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs index 02546d356..7973f664b 100644 --- a/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Rule.Interfaces; @@ -41,15 +42,30 @@ namespace Ombi.Core.Rule.Rules.Request { continue; } + + var episodesToRemove = new List(); foreach (var e in season.Episodes) { - var hasEpisode = currentSeasonRequest.Episodes.Any(x => x.EpisodeNumber == e.EpisodeNumber); - if (hasEpisode) + var existingEpRequest = currentSeasonRequest.Episodes.FirstOrDefault(x => x.EpisodeNumber == e.EpisodeNumber); + if (existingEpRequest != null) { - return Fail($"We already have episodes requested from series {tv.Title}"); + episodesToRemove.Add(e); } } + + episodesToRemove.ForEach(x => + { + season.Episodes.Remove(x); + }); + } + + var anyEpisodes = tv.SeasonRequests.SelectMany(x => x.Episodes).Any(); + + if (!anyEpisodes) + { + return Fail($"We already have episodes requested from series {tv.Title}"); } + } return Success(); } diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs index 71331c51d..fe062e851 100644 --- a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs +++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs @@ -13,12 +13,12 @@ namespace Ombi.Core.Rule.Rules.Search { public static void CheckForUnairedEpisodes(SearchTvShowViewModel search) { - foreach (var season in search.SeasonRequests) + foreach (var season in search.SeasonRequests.ToList()) { // If we have all the episodes for this season, then this season is available if (season.Episodes.All(x => x.Available)) { - season.SeasonAvailable = true; + season.SeasonAvailable = true; } } if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 3fe11cbc4..7f6718a6b 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -67,7 +67,7 @@ namespace Ombi.Core.Rule.Rules.Search var s = await EmbySettings.GetSettingsAsync(); if (s.Enable) { - var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null); + var server = s.Servers.FirstOrDefault(); if ((server?.ServerHostname ?? string.Empty).HasValue()) { obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname); diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs index 3ca57d635..ca0c0cd97 100644 --- a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs @@ -9,7 +9,7 @@ using Ombi.Store.Repository; namespace Ombi.Core.Rule.Rules.Search { - public class LidarrAlbumCacheRule : BaseSearchRule, IRules + public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule { public LidarrAlbumCacheRule(IExternalRepository db) { @@ -18,7 +18,9 @@ namespace Ombi.Core.Rule.Rules.Search private readonly IExternalRepository _db; - public Task Execute(SearchViewModel objec) + public override SpecificRules Rule => SpecificRules.LidarrAlbum; + + public Task Execute(object objec, string requestOnBehalf) { if (objec is SearchAlbumViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs index a254ea2d3..06ca3f06c 100644 --- a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs @@ -17,7 +17,7 @@ namespace Ombi.Core.Rule.Rules.Search private readonly IExternalRepository _db; - public Task Execute(object objec) + public Task Execute(object objec, string requestOnBehalf) { var obj = (SearchArtistViewModel) objec; // Check if it's in Lidarr @@ -30,6 +30,7 @@ namespace Ombi.Core.Rule.Rules.Search return Task.FromResult(Success()); } + public override SpecificRules Rule => SpecificRules.LidarrArtist; } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs index d0397cc15..5a2b6ed98 100644 --- a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs @@ -27,9 +27,12 @@ namespace Ombi.Core.Rule.Rules.Search var useTheMovieDb = false; var useId = false; var useTvDb = false; + + PlexMediaTypeEntity type = ConvertType(obj.Type); + if (obj.ImdbId.HasValue()) { - item = await PlexContentRepository.Get(obj.ImdbId); + item = await PlexContentRepository.GetByType(obj.ImdbId, ProviderType.ImdbId, type); if (item != null) { useImdb = true; @@ -39,7 +42,7 @@ namespace Ombi.Core.Rule.Rules.Search { if (obj.Id > 0) { - item = await PlexContentRepository.Get(obj.Id.ToString()); + item = await PlexContentRepository.GetByType(obj.Id.ToString(), ProviderType.TheMovieDbId, type); if (item != null) { useId = true; @@ -47,7 +50,7 @@ namespace Ombi.Core.Rule.Rules.Search } if (obj.TheMovieDbId.HasValue()) { - item = await PlexContentRepository.Get(obj.TheMovieDbId); + item = await PlexContentRepository.GetByType(obj.TheMovieDbId, ProviderType.TheMovieDbId, type); if (item != null) { useTheMovieDb = true; @@ -58,7 +61,7 @@ namespace Ombi.Core.Rule.Rules.Search { if (obj.TheTvDbId.HasValue()) { - item = await PlexContentRepository.Get(obj.TheTvDbId); + item = await PlexContentRepository.GetByType(obj.TheTvDbId, ProviderType.TvDbId, type); if (item != null) { useTvDb = true; @@ -85,9 +88,9 @@ namespace Ombi.Core.Rule.Rules.Search if (search.SeasonRequests.Any()) { var allEpisodes = PlexContentRepository.GetAllEpisodes(); - foreach (var season in search.SeasonRequests) + foreach (var season in search.SeasonRequests.ToList()) { - foreach (var episode in season.Episodes) + foreach (var episode in season.Episodes.ToList()) { await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log); } @@ -100,6 +103,12 @@ namespace Ombi.Core.Rule.Rules.Search return Success(); } - + private PlexMediaTypeEntity ConvertType(RequestType type) => + type switch + { + RequestType.Movie => PlexMediaTypeEntity.Movie, + RequestType.TvShow => PlexMediaTypeEntity.Show, + _ => PlexMediaTypeEntity.Movie, + }; } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs index 0e4313ac7..325b7cc8d 100644 --- a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Models.Search; @@ -6,6 +7,7 @@ using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; namespace Ombi.Core.Rule.Rules { @@ -23,7 +25,7 @@ namespace Ombi.Core.Rule.Rules if (obj.RequestType == RequestType.TvShow) { var vm = (ChildRequests) obj; - var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == vm.Id); // TODO lookup the external provider in the sonarr sync to use themoviedb + var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TheMovieDbId == vm.Id); if (result != null) { if (vm.SeasonRequests.Any()) @@ -31,17 +33,30 @@ namespace Ombi.Core.Rule.Rules var sonarrEpisodes = _ctx.SonarrEpisodeCache; foreach (var season in vm.SeasonRequests) { + var toRemove = new List(); foreach (var ep in season.Episodes) { // Check if we have it - var monitoredInSonarr = sonarrEpisodes.Any(x => + var monitoredInSonarr = sonarrEpisodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == season.SeasonNumber - && x.TvDbId == vm.Id); - if (monitoredInSonarr) + && x.MovieDbId == vm.Id); + if (monitoredInSonarr != null) { - return new RuleResult{Message = "We already have this request, please choose the \"Select...\" option to refine your request"}; - } + toRemove.Add(ep); + } } + + toRemove.ForEach(x => + { + season.Episodes.Remove(x); + }); + + } + var anyEpisodes = vm.SeasonRequests.SelectMany(x => x.Episodes).Any(); + + if (!anyEpisodes) + { + return new RuleResult { Message = $"We already have episodes requested from series {vm.Title}" }; } } } diff --git a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs index 30ec9b14a..af71df9ac 100644 --- a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs +++ b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs @@ -22,11 +22,20 @@ namespace Ombi.Core.Rule.Rules.Specific private OmbiUserManager UserManager { get; } private ISettingsService Settings { get; } - public async Task Execute(object obj) + public async Task Execute(object obj, string requestOnBehalf) { var req = (BaseRequest)obj; + var canRequestonBehalf = requestOnBehalf.HasValue(); var settings = await Settings.GetSettingsAsync(); var sendNotification = true; + + if (settings.DoNotSendNotificationsForAutoApprove && canRequestonBehalf) + { + return new RuleResult + { + Success = false + }; + } var requestedUser = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == req.RequestedUserId); if (req.RequestType == RequestType.Movie) { diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index 33d6afd25..87d686d35 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -125,7 +125,6 @@ namespace Ombi.Core.Senders private async Task SendToRadarr(MovieRequests model, RadarrSettings settings) { - var v3 = settings.V3; var qualityToUse = int.Parse(settings.DefaultQualityProfile); var rootFolderPath = settings.DefaultRootPath; @@ -159,30 +158,16 @@ namespace Ombi.Core.Senders List movies; // Check if the movie already exists? Since it could be unmonitored - if (settings.V3) - { - movies = await _radarrV3Api.GetMovies(settings.ApiKey, settings.FullUri); - } - else - { - movies = await _radarrV2Api.GetMovies(settings.ApiKey, settings.FullUri); - } + + movies = await _radarrV3Api.GetMovies(settings.ApiKey, settings.FullUri); + var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId); if (existingMovie == null) { - RadarrAddMovie result; - if (v3) - { - result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, - qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, - settings.MinimumAvailability); - } - else - { - result = await _radarrV2Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, - qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, - settings.MinimumAvailability); - } + var result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, + qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, + settings.MinimumAvailability); + if (!string.IsNullOrEmpty(result.Error?.message)) { _log.LogError(LoggingEvents.RadarrCacher, result.Error.message); @@ -199,23 +184,12 @@ namespace Ombi.Core.Senders { // let's set it to monitored and search for it existingMovie.monitored = true; - if (v3) - { - await _radarrV3Api.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); - // Search for it - if (!settings.AddOnly) - { - await _radarrV3Api.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); - } - } - else + + await _radarrV3Api.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); + // Search for it + if (!settings.AddOnly) { - await _radarrV2Api.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); - // Search for it - if (!settings.AddOnly) - { - await _radarrV2Api.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); - } + await _radarrV3Api.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); } return new SenderResult { Success = true, Sent = true }; @@ -226,18 +200,9 @@ namespace Ombi.Core.Senders private async Task RadarrRootPath(int overrideId, RadarrSettings settings) { - if (settings.V3) - { - var paths = await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri); - var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); - return selectedPath?.path ?? string.Empty; - } - else - { - var paths = await _radarrV2Api.GetRootFolders(settings.ApiKey, settings.FullUri); - var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); - return selectedPath?.path ?? string.Empty; - } + var paths = await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri); + var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); + return selectedPath?.path ?? string.Empty; } } } \ No newline at end of file diff --git a/src/Ombi.HealthChecks/Checks/RadarrHealthCheck.cs b/src/Ombi.HealthChecks/Checks/RadarrHealthCheck.cs index e365c73bb..9d5b31e5f 100644 --- a/src/Ombi.HealthChecks/Checks/RadarrHealthCheck.cs +++ b/src/Ombi.HealthChecks/Checks/RadarrHealthCheck.cs @@ -21,7 +21,7 @@ namespace Ombi.HealthChecks.Checks using (var scope = CreateScope()) { var settingsProvider = scope.ServiceProvider.GetRequiredService>(); - var api = scope.ServiceProvider.GetRequiredService(); + var api = scope.ServiceProvider.GetRequiredService(); var settings = await settingsProvider.GetSettingsAsync(); if (!settings.Enabled) { diff --git a/src/Ombi.Helpers.Tests/PlexHelperTests.cs b/src/Ombi.Helpers.Tests/PlexHelperTests.cs index 82b59fdf7..e7d35a583 100644 --- a/src/Ombi.Helpers.Tests/PlexHelperTests.cs +++ b/src/Ombi.Helpers.Tests/PlexHelperTests.cs @@ -61,6 +61,7 @@ namespace Ombi.Helpers.Tests get { yield return new TestCaseData("plex://movie/5e1632df2d4d84003e48e54e|imdb://tt9178402|tmdb://610201", new ProviderId { ImdbId = "tt9178402", TheMovieDb = "610201" }).SetName("V2 Regular Plex Id"); + yield return new TestCaseData("plex://movie/5e1632df2d4d84003e48e54e|imdb://tt9178402|tmdb://610201|thetvdb://12345", new ProviderId { ImdbId = "tt9178402", TheMovieDb = "610201", TheTvDb = "12345" }).SetName("V2 Regular Plex Id w/ tvdb"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|imdb://tt0119567|tmdb://330", new ProviderId { ImdbId = "tt0119567", TheMovieDb = "330" }).SetName("V2 Regular Plex Id Another"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|imdb://tt0119567", new ProviderId { ImdbId = "tt0119567" }).SetName("V2 Regular Plex Id Single Imdb"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|tmdb://330", new ProviderId { TheMovieDb = "330" }).SetName("V2 Regular Plex Id Single Tmdb"); diff --git a/src/Ombi.Helpers/DemoSingleton.cs b/src/Ombi.Helpers/DemoSingleton.cs index 22b6b2f31..b94c7dbb3 100644 --- a/src/Ombi.Helpers/DemoSingleton.cs +++ b/src/Ombi.Helpers/DemoSingleton.cs @@ -1,4 +1,6 @@ -namespace Ombi.Helpers +using System.Collections.Generic; + +namespace Ombi.Helpers { public class DemoSingleton { @@ -10,4 +12,460 @@ public bool Demo { get; set; } } + + public static class ExcludedDemo + { + public static HashSet ExcludedContent => new HashSet + { + "101 Dalmatians", + "102 Dalmatians", + "20,000 Leagues Under the Sea", + "A Bug's Life", + "A Far Off Place", + "A Goofy Movie", + "A Kid in King Arthur's Court", + "A Tale of Two Critters", + "A Tiger Walks", + "A Wrinkle in Time", + "ABCD 2", + "African Cats", + "Air Bud", + "Air Bud: Golden Receiver", + "Aladdin", + "Aladdin", + "Alexander and the Terrible, Horrible, No Good, Very Bad Day", + "Alice Through the Looking Glass", + "Alice in Wonderland", + "Alice in Wonderland", + "Aliens of the Deep", + "Almost Angels", + "America's Heart and Soul", + "Amy", + "Anaganaga O Dheerudu", + "Angels in the Outfield", + "Arjun: The Warrior Prince", + "Around the World in 80 Days", + "Artemis Fowl", + "Atlantis: The Lost Empire", + "Babes in Toyland", + "Bambi", + "Bears", + "Beauty and the Beast", + "Beauty and the Beast", + "Bedknobs and Broomsticks", + "Bedtime Stories", + "Benji the Hunted", + "Beverly Hills Chihuahua", + "Big Hero 6", + "Big Red", + "Blackbeard's Ghost", + "Blank Check", + "Blue", + "Bolt", + "Bon Voyage!", + "Born in China", + "Brave", + "Bridge to Terabithia", + "Brother Bear", + "Candleshoe", + "Cars", + "Cars 2", + "Cars 3", + "Charley and the Angel", + "Charlie, the Lonesome Cougar", + "Cheetah", + "Chicken Little", + "Chimpanzee", + "Christopher Robin", + "Cinderella", + "Cinderella", + "Coco", + "College Road Trip", + "Condorman", + "Confessions of a Teenage Drama Queen", + "Cool Runnings", + "D2: The Mighty Ducks", + "D3: The Mighty Ducks", + "Dangal", + "Darby O'Gill and the Little People", + "Dasavathaaram", + "Davy Crockett and the River Pirates", + "Davy Crockett, King of the Wild Frontier", + "Dinosaur", + "Disney's A Christmas Carol", + "Disney's The Kid", + "Do Dooni Chaar", + "Dolphin Reef", + "Doug's 1st Movie", + "Dragonslayer", + "DuckTales the Movie: Treasure of the Lost Lamp", + "Dumbo", + "Dumbo", + "Earth", + "Eight Below", + "Emil and the Detectives", + "Enchanted", + "Endurance", + "Escape to Witch Mountain", + "Expedition China", + "Fantasia", + "Fantasia 2000", + "Finding Dory", + "Finding Nemo", + "First Kid", + "Flight of the Navigator", + "Flubber", + "Follow Me, Boys!", + "Frank and Ollie", + "Frankenweenie", + "Freaky Friday", + "Freaky Friday", + "Frozen", + "Frozen II", + "Onward", + "Star Wars", + "Raya", + "Mandalorian", + "Fun and Fancy Free", + "G-Force", + "George of the Jungle", + "Ghost in the Shell 2: Innocence GITS2", + "Ghost of the Mountains", + "Ghosts of the Abyss", + "Glory Road", + "Greyfriars Bobby", + "Growing Up Wild", + "Gus", + "Hannah Montana and Miley Cyrus: Best of Both Worlds Concert", + "Hannah Montana: The Movie", + "Heavyweights", + "Herbie Goes Bananas", + "Herbie Goes to Monte Carlo", + "Herbie Rides Again", + "Herbie: Fully Loaded", + "Hercules", + "High School Musical 3: Senior Year", + "Hocus Pocus", + "Holes", + "Home on the Range", + "Homeward Bound II: Lost in San Francisco", + "Homeward Bound: The Incredible Journey", + "Honey, I Blew Up the Kid", + "Honey, I Shrunk the Kids", + "Hot Lead and Cold Feet", + "I'll Be Home for Christmas", + "Ice Princess", + "In Search of the Castaways", + "Incredibles 2", + "Inside Out", + "Inspector Gadget", + "Into the Woods", + "Invincible", + "Iron Will", + "Jagga Jasoos", + "James and the Giant Peach", + "John Carter", + "Johnny Tremain", + "Jonas Brothers: The 3D Concert Experience", + "Jungle 2 Jungle", + "Jungle Cat", + "Khoobsurat", + "Kidnapped", + "King of the Grizzlies", + "L'Empereur - March of the Penguins 2: The Next Step[a]", + "Lady and the Tramp", + "Lady and the Tramp", + "Lilly the Witch: The Dragon and the Magic Book", + "Lilly the Witch: The Journey to Mandolan", + "Lilo & Stitch", + "Lt. Robin Crusoe, U.S.N.", + "Make Mine Music", + "Maleficent", + "Maleficent: Mistress of Evil", + "Man of the House", + "Mars Needs Moms", + "Mary Poppins", + "Mary Poppins Returns", + "Max Keeble's Big Move", + "McFarland, USA", + "Meet the Deedles", + "Meet the Robinsons", + "Melody Time", + "Midnight Madness", + "Mighty Joe Young", + "Million Dollar Arm", + "Miracle", + "Miracle of the White Stallions", + "Moana", + "Monkey Kingdom", + "Monkeys, Go Home!", + "Monsters University", + "Monsters, Inc.", + "Moon Pilot", + "Morning Light", + "Mr. Magoo", + "Mulan", + "Muppet Treasure Island", + "Muppets Most Wanted", + "My Favorite Martian", + "Napoleon and Samantha", + "National Treasure", + "National Treasure: Book of Secrets", + "Never Cry Wolf", + "Never a Dull Moment", + "Newsies", + "Night Crossing", + "Nikki, Wild Dog of the North", + "No Deposit, No Return", + "Now You See Him, Now You Don't", + "Oceans", + "Old Dogs", + "Old Yeller", + "Oliver & Company", + "One Hundred and One Dalmatians", + "One Little Indian", + "One Magic Christmas", + "One of Our Dinosaurs Is Missing", + "Operation Dumbo Drop", + "Oz the Great and Powerful", + "Penguins", + "Perri", + "Pete's Dragon", + "Pete's Dragon", + "Peter Pan", + "Piglet's Big Movie", + "Pinocchio", + "Pirates of the Caribbean: At World's End", + "Pirates of the Caribbean: Dead Man's Chest", + "Pirates of the Caribbean: Dead Men Tell No Tales", + "Pirates of the Caribbean: On Stranger Tides", + "Pirates of the Caribbean: The Curse of the Black Pearl", + "Planes", + "Planes: Fire & Rescue", + "Pocahontas", + "Pollyanna", + "Pooh's Heffalump Movie", + "Popeye", + "Prince of Persia: The Sands of Time", + "Prom", + "Queen of Katwe", + "Race to Witch Mountain", + "Ralph Breaks the Internet", + "Rascal", + "Ratatouille", + "Recess: School's Out", + "Remember the Titans", + "Return from Witch Mountain", + "Return to Never Land", + "Return to Oz", + "Return to Snowy River", + "Ride a Wild Pony", + "Roadside Romeo", + "Rob Roy, the Highland Rogue", + "Robin Hood", + "RocketMan", + "Roving Mars", + "Run, Cougar, Run", + "Sacred Planet", + "Saludos Amigos", + "Savage Sam", + "Saving Mr. Banks", + "Scandalous John", + "Secretariat", + "Secrets of Life", + "Shipwrecked", + "Sky High", + "Sleeping Beauty", + "Smith!", + "Snow Dogs", + "Snow White and the Seven Dwarfs", + "Snowball Express", + "So Dear to My Heart", + "Something Wicked This Way Comes", + "Son of Flubber", + "Song of the South", + "Squanto: A Warrior's Tale", + "Summer Magic", + "Superdad", + "Swiss Family Robinson", + "Tall Tale", + "Tangled", + "Tarzan", + "Teacher's Pet", + "Ten Who Dared", + "Tex", + "That Darn Cat", + "That Darn Cat!", + "The Absent-Minded Professor", + "The Adventures of Bullwhip Griffin", + "The Adventures of Huck Finn", + "The Adventures of Ichabod and Mr. Toad", + "The African Lion", + "The Apple Dumpling Gang", + "The Apple Dumpling Gang Rides Again", + "The Aristocats", + "The BFG", + "The Barefoot Executive", + "The Bears and I", + "The Best of Walt Disney's True-Life Adventures", + "The Big Green", + "The Biscuit Eater", + "The Black Cauldron", + "The Black Hole", + "The Boatniks", + "The Book of Masters", + "The Boys: The Sherman Brothers' Story", + "The Castaway Cowboy", + "The Cat from Outer Space", + "The Chronicles of Narnia: Prince Caspian", + "The Chronicles of Narnia: The Lion, the Witch and the Wardrobe", + "The Computer Wore Tennis Shoes", + "The Country Bears", + "The Crimson Wing: Mystery of the Flamingos", + "The Devil and Max Devlin", + "The Emperor's New Groove", + "The Fighting Prince of Donegal", + "The Finest Hours", + "The Fox and the Hound", + "The Game Plan", + "The Gnome-Mobile", + "The Good Dinosaur", + "The Great Locomotive Chase", + "The Great Mouse Detective", + "The Greatest Game Ever Played", + "The Happiest Millionaire", + "The Haunted Mansion", + "The Horse in the Gray Flannel Suit", + "The Hunchback of Notre Dame", + "The Incredible Journey", + "The Incredibles", + "The Island at the Top of the World", + "The Journey of Natty Gann", + "The Jungle Book", + "The Jungle Book", + "The Jungle Book", + "The Jungle Book 2", + "The Last Flight of Noah's Ark", + "The Legend of Lobo", + "The Light in the Forest", + "The Lion King", + "The Lion King", + "The Little Mermaid", + "The Littlest Horse Thieves", + "The Littlest Outlaw", + "The Living Desert", + "The Lizzie McGuire Movie", + "The London Connection", + "The Lone Ranger", + "The Love Bug", + "The Many Adventures of Winnie the Pooh", + "The Mighty Ducks", + "The Million Dollar Duck", + "The Misadventures of Merlin Jones", + "The Monkey's Uncle", + "The Moon-Spinners", + "The Muppet Christmas Carol", + "The Muppets", + "The Nightmare Before Christmas 3D TNBC", + "The North Avenue Irregulars", + "The Nutcracker and the Four Realms", + "The Odd Life of Timothy Green", + "The One and Only, Genuine, Original Family Band", + "The Pacifier", + "The Parent Trap", + "The Parent Trap", + "The Pixar Story", + "The Princess Diaries", + "The Princess Diaries 2: Royal Engagement", + "The Princess and the Frog", + "The Reluctant Dragon", + "The Rescuers", + "The Rescuers Down Under", + "The Rocketeer TR", + "The Rookie", + "The Santa Clause 2", + "The Santa Clause 3: The Escape Clause", + "The Santa Clause TSC", + "The Shaggy D.A.", + "The Shaggy Dog", + "The Shaggy Dog", + "The Sign of Zorro", + "The Sorcerer's Apprentice", + "The Story of Robin Hood and His Merrie Men", + "The Straight Story", + "The Strongest Man in the World", + "The Sword and the Rose", + "The Sword in the Stone", + "The Three Caballeros", + "The Three Lives of Thomasina", + "The Three Musketeers", + "The Tigger Movie", + "The Ugly Dachshund", + "The Vanishing Prairie", + "The Watcher in the Woods", + "The Wild", + "The Wild Country", + "The World's Greatest Athlete", + "The Young Black Stallion", + "Third Man on the Mountain", + "Those Calloways", + "Toby Tyler", + "Tom and Huck", + "Tomorrowland", + "Tonka", + "Toy Story", + "Toy Story 2", + "Toy Story 3", + "Toy Story 4", + "Trail of the Panda", + "Treasure Island", + "Treasure Planet", + "Treasure of Matecumbe", + "Trenchcoat", + "Tron", + "Tron: Legacy", + "Tuck Everlasting", + "Underdog", + "Unidentified Flying Oddball", + "Up", + "Valiant", + "Victory Through Air Power", + "WALL-E", + "Waking Sleeping Beauty", + "Walt & El Grupo", + "Westward Ho the Wagons!", + "White Fang", + "White Fang 2: Myth of the White Wolf", + "White Wilderness", + "Wild Hearts Can't Be Broken", + "Wings of Life", + "Winnie the Pooh", + "Wreck-It Ralph", + "Zokkomon", + "Zootopia", + "Zorro the Avenger", + "Iron Man", + "Hulk", + "Thor", + "Avengers", + "Guardians of the Galaxy", + "Ant-Man", + "Captain America", + "Doctor Strange", + "Guardians of the Galaxy", + "Spider-Man", + "Black Panther", + "Marvel", + "Spider Man", + "SpiderMan", + "Loki", + "Winter Soldier", + "Wanda", + "Small Fry", + "Rex", + "Lamp life", + "Toy", + "Hawaiian" + }; + } } \ No newline at end of file diff --git a/src/Ombi.Helpers/PlexHelper.cs b/src/Ombi.Helpers/PlexHelper.cs index 08548b8c9..506e0939f 100644 --- a/src/Ombi.Helpers/PlexHelper.cs +++ b/src/Ombi.Helpers/PlexHelper.cs @@ -107,7 +107,7 @@ namespace Ombi.Helpers public static string GetPlexMediaUrl(string machineId, int mediaId) { var url = - $"https://app.plex.tv/web/app#!/server/{machineId}/details?key=library%2Fmetadata%2F{mediaId}"; + $"https://app.plex.tv/web/app#!/server/{machineId}/details?key=%2flibrary%2Fmetadata%2F{mediaId}"; return url; } diff --git a/src/Ombi.Mapping/Profiles/MovieProfile.cs b/src/Ombi.Mapping/Profiles/MovieProfile.cs index 161cb1fda..0551abd58 100644 --- a/src/Ombi.Mapping/Profiles/MovieProfile.cs +++ b/src/Ombi.Mapping/Profiles/MovieProfile.cs @@ -104,7 +104,7 @@ namespace Ombi.Mapping.Profiles .ForMember(x => x.Id, o => o.MapFrom(s => s.id)) .ForMember(x => x.Overview, o => o.MapFrom(s => s.overview)) .ForMember(x => x.PosterPath, o => o.MapFrom(s => s.poster_path)) - .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => DateTime.Parse(s.release_date))) + .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => string.IsNullOrEmpty(s.release_date) ? (DateTime?)null : DateTime.Parse(s.release_date))) .ForMember(x => x.Title, o => o.MapFrom(s => s.title)); CreateMap().ReverseMap(); diff --git a/src/Ombi.Mapping/Profiles/TvProfileV2.cs b/src/Ombi.Mapping/Profiles/TvProfileV2.cs index d54321d01..09021efb2 100644 --- a/src/Ombi.Mapping/Profiles/TvProfileV2.cs +++ b/src/Ombi.Mapping/Profiles/TvProfileV2.cs @@ -32,7 +32,7 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.Images, opts => opts.MapFrom(src => src.Images)) .ForMember(dest => dest.Cast, opts => opts.MapFrom(src => src.Credits.cast)) .ForMember(dest => dest.Crew, opts => opts.MapFrom(src => src.Credits.crew)) - .ForMember(dest => dest.Banner, opts => opts.MapFrom(src => GetBanner(src.Images))) + .ForMember(dest => dest.Banner, opts => opts.MapFrom(src => GetBanner(src.Images, src.backdrop_path))) .ForMember(dest => dest.Genres, opts => opts.MapFrom(src => src.genres)) .ForMember(dest => dest.Keywords, opts => opts.MapFrom(src => src.Keywords)) .ForMember(dest => dest.Tagline, opts => opts.MapFrom(src => src.tagline)) @@ -78,20 +78,20 @@ namespace Ombi.Mapping.Profiles CreateMap().ReverseMap(); } - private string GetBanner(Api.TheMovieDb.Models.Images images) + private string GetBanner(Api.TheMovieDb.Models.Images images, string backdropPath) { var hasBackdrop = images?.Backdrops?.Any(); if (hasBackdrop ?? false) { return images.Backdrops?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).Select(x => x.FilePath).FirstOrDefault(); } - else if (images != null) + else if (images?.Posters?.Any() ?? false) { return images.Posters?.OrderBy(x => x.VoteCount).ThenBy(x => x.VoteAverage).Select(x => x.FilePath).FirstOrDefault(); } else { - return string.Empty; + return backdropPath; } } diff --git a/src/Ombi.Notifications.Templates/INewsletterTemplate.cs b/src/Ombi.Notifications.Templates/INewsletterTemplate.cs index e3302710d..620cf3999 100644 --- a/src/Ombi.Notifications.Templates/INewsletterTemplate.cs +++ b/src/Ombi.Notifications.Templates/INewsletterTemplate.cs @@ -2,6 +2,6 @@ { public interface INewsletterTemplate { - string LoadTemplate(string subject, string intro, string tableHtml, string logo); + string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink); } } \ No newline at end of file diff --git a/src/Ombi.Notifications.Templates/NewsletterTemplate.cs b/src/Ombi.Notifications.Templates/NewsletterTemplate.cs index 84d92c21c..f2f0cbf12 100644 --- a/src/Ombi.Notifications.Templates/NewsletterTemplate.cs +++ b/src/Ombi.Notifications.Templates/NewsletterTemplate.cs @@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates if (string.IsNullOrEmpty(_templateLocation)) { #if DEBUG - _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp3.0", "Templates", "NewsletterTemplate.html"); + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates", "NewsletterTemplate.html"); #else _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html"); #endif @@ -29,9 +29,10 @@ namespace Ombi.Notifications.Templates private const string Logo = "{@LOGO}"; private const string TableLocation = "{@RECENTLYADDED}"; private const string IntroText = "{@INTRO}"; + private const string Unsubscribe = "{@UNSUBSCRIBE}"; - public string LoadTemplate(string subject, string intro, string tableHtml, string logo) + public string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink) { var sb = new StringBuilder(File.ReadAllText(TemplateLocation)); sb.Replace(SubjectKey, subject); @@ -39,6 +40,7 @@ namespace Ombi.Notifications.Templates sb.Replace(IntroText, intro); sb.Replace(DateKey, DateTime.Now.ToString("f")); sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo); + sb.Replace(Unsubscribe, string.IsNullOrEmpty(unsubscribeLink) ? string.Empty : unsubscribeLink); return sb.ToString(); } diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 10b76dd30..71666ba67 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -451,6 +451,11 @@