diff --git a/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs index 7c2053377..bce97d36a 100644 --- a/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs @@ -16,17 +16,19 @@ namespace NzbDrone.Common.Cloud Services = new HttpRequestBuilder("https://radarr.lidarr.audio/v1/") .CreateFactory(); - TMDB = new HttpRequestBuilder("https://api.themoviedb.org/3/{route}/{id}{secondaryRoute}") - .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") + TMDB = new HttpRequestBuilder("https://api.themoviedb.org/{api}/{route}/{id}{secondaryRoute}") + .SetHeader("Authorization", $"Bearer {AuthToken}") .CreateFactory(); TMDBSingle = new HttpRequestBuilder("https://api.themoviedb.org/3/{route}") - .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") + .SetHeader("Authorization", $"Bearer {AuthToken}") .CreateFactory(); } public IHttpRequestBuilderFactory Services { get; private set; } public IHttpRequestBuilderFactory TMDB { get; private set; } public IHttpRequestBuilderFactory TMDBSingle { get; private set; } + + public string AuthToken => "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxYTczNzMzMDE5NjFkMDNmOTdmODUzYTg3NmRkMTIxMiIsInN1YiI6IjU4NjRmNTkyYzNhMzY4MGFiNjAxNzUzNCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.gh1BwogCCKOda6xj9FRMgAAj_RYKMMPC3oNlcBtlmwk"; } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs index fa0b409b2..9bdbb5685 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -15,6 +15,17 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public int total_pages { get; set; } } + public class AuthRefreshTokenResponse + { + public string request_token { get; set; } + } + + public class AuthAccessTokenResponse + { + public string access_token { get; set; } + public string account_id { get; set; } + } + public class MovieResult { public string poster_path { get; set; } @@ -37,6 +48,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public string physical_release_note { get; set; } } + public class CreditsResult : MovieResult + { + public string department { get; set; } + public string job { get; set; } + public string credit_id { get; set; } + } + public class MovieResourceRoot { public bool adult { get; set; } @@ -181,17 +199,31 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public class ListResponseRoot { - public string created_by { get; set; } - public string description { get; set; } - public int favorite_count { get; set; } public string id { get; set; } - public Item[] items { get; set; } - public int item_count { get; set; } + public Item[] results { get; set; } + public int total_results { get; set; } public string iso_639_1 { get; set; } public string name { get; set; } public object poster_path { get; set; } } + public class CollectionResponseRoot + { + public int id { get; set; } + public string name { get; set; } + public string overview { get; set; } + public string poster_path { get; set; } + public string backdrop_path { get; set; } + public MovieResult[] parts { get; set; } + } + + public class PersonCreditsRoot + { + public CreditsResult[] cast { get; set; } + public CreditsResult[] crew { get; set; } + public int id { get; set; } + } + public class Item : MovieResult { public string media_type { get; set; } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index c6ca0689c..2421da783 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -63,6 +63,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var startDate = startTime.ToString("o"); var request = _movieBuilder.Create() + .SetSegment("api", "3") .SetSegment("route", "movie") .SetSegment("id", "") .SetSegment("secondaryRoute", "changes") @@ -82,6 +83,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var langCode = profile != null ? IsoLanguages.Get(profile.Language)?.TwoLetterCode ?? "en" : "en"; var request = _movieBuilder.Create() + .SetSegment("api", "3") .SetSegment("route", "movie") .SetSegment("id", tmdbId.ToString()) .SetSegment("secondaryRoute", "") @@ -329,6 +331,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public Movie GetMovieInfo(string imdbId) { var request = _movieBuilder.Create() + .SetSegment("api", "3") .SetSegment("route", "find") .SetSegment("id", imdbId) .SetSegment("secondaryRoute", "") @@ -504,6 +507,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var firstChar = searchTerm.First(); var request = _movieBuilder.Create() + .SetSegment("api", "3") .SetSegment("route", "search") .SetSegment("id", "movie") .SetSegment("secondaryRoute", "") diff --git a/src/NzbDrone.Core/NetImport/NetImportFactory.cs b/src/NzbDrone.Core/NetImport/NetImportFactory.cs index 4975dcbae..00773945c 100644 --- a/src/NzbDrone.Core/NetImport/NetImportFactory.cs +++ b/src/NzbDrone.Core/NetImport/NetImportFactory.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.NetImport public List Discoverable() { - var enabledImporters = GetAvailableProviders().Where(n => (n.GetType() == typeof(Radarr.RadarrLists) || n.GetType() == typeof(TMDb.TMDbImport))); + var enabledImporters = GetAvailableProviders().Where(n => (n.GetType() == typeof(Radarr.RadarrLists) || n.GetType() == typeof(TMDb.Popular.TMDbPopularImport))); var indexers = FilterBlockedIndexers(enabledImporters); return indexers.ToList(); } diff --git a/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionImport.cs b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionImport.cs new file mode 100644 index 000000000..99c4ad3f3 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionImport.cs @@ -0,0 +1,42 @@ +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb.Collection +{ + public class TMDbCollectionImport : TMDbNetImportBase + { + public TMDbCollectionImport(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie searchForNewMovie, + Logger logger) + : base(requestBuilder, httpClient, configService, parsingService, searchForNewMovie, logger) + { + } + + public override string Name => "TMDb Collection"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public override IParseNetImportResponse GetParser() + { + return new TMDbCollectionParser(_skyhookProxy); + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbCollectionRequestGenerator() + { + RequestBuilder = _requestBuilder, + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionParser.cs b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionParser.cs new file mode 100644 index 000000000..ed2a1e508 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionParser.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.NetImport.TMDb.Collection +{ + public class TMDbCollectionParser : TMDbParser + { + private readonly ISearchForNewMovie _skyhookProxy; + + public TMDbCollectionParser(ISearchForNewMovie skyhookProxy) + : base(skyhookProxy) + { + _skyhookProxy = skyhookProxy; + } + + public override IList ParseResponse(NetImportResponse importResponse) + { + var movies = new List(); + + if (!PreProcess(importResponse)) + { + return movies; + } + + var jsonResponse = JsonConvert.DeserializeObject(importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var movie in jsonResponse.parts) + { + // Movies with no Year Fix + if (string.IsNullOrWhiteSpace(movie.release_date)) + { + continue; + } + + movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); + } + + return movies; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionRequestGenerator.cs new file mode 100644 index 000000000..b48b828ab --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionRequestGenerator.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.TMDb.Collection +{ + public class TMDbCollectionRequestGenerator : INetImportRequestGenerator + { + public TMDbCollectionSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public IHttpRequestBuilderFactory RequestBuilder { get; set; } + public Logger Logger { get; set; } + + public TMDbCollectionRequestGenerator() + { + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMoviesRequest()); + + return pageableRequests; + } + + private IEnumerable GetMoviesRequest() + { + Logger.Info($"Importing TMDb movies from collection: {Settings.CollectionId}"); + + yield return new NetImportRequest(RequestBuilder.Create() + .SetSegment("api", "3") + .SetSegment("route", "collection") + .SetSegment("id", Settings.CollectionId) + .SetSegment("secondaryRoute", "") + .Build()); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionSettings.cs new file mode 100644 index 000000000..92d95d3f4 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Collection/TMDbCollectionSettings.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb.Collection +{ + public class TMDbCollectionSettingsValidator : TMDbSettingsBaseValidator + { + public TMDbCollectionSettingsValidator() + : base() + { + RuleFor(c => c.CollectionId).Matches(@"^[1-9][0-9]*$", RegexOptions.IgnoreCase); + } + } + + public class TMDbCollectionSettings : TMDbSettingsBase + { + protected override AbstractValidator Validator => new TMDbCollectionSettingsValidator(); + + public TMDbCollectionSettings() + { + CollectionId = ""; + } + + [FieldDefinition(1, Label = "Collection Id", Type = FieldType.Textbox, HelpText = "TMDb Id of Collection to Follow")] + public string CollectionId { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListImport.cs b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListImport.cs new file mode 100644 index 000000000..37325b085 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListImport.cs @@ -0,0 +1,42 @@ +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb.List +{ + public class TMDbListImport : TMDbNetImportBase + { + public TMDbListImport(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie searchForNewMovie, + Logger logger) + : base(requestBuilder, httpClient, configService, parsingService, searchForNewMovie, logger) + { + } + + public override string Name => "TMDb List"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public override IParseNetImportResponse GetParser() + { + return new TMDbListParser(_skyhookProxy); + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbListRequestGenerator() + { + RequestBuilder = _requestBuilder, + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListParser.cs b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListParser.cs new file mode 100644 index 000000000..fe57a0201 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListParser.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.NetImport.TMDb.List +{ + public class TMDbListParser : TMDbParser + { + private readonly ISearchForNewMovie _skyhookProxy; + + public TMDbListParser(ISearchForNewMovie skyhookProxy) + : base(skyhookProxy) + { + _skyhookProxy = skyhookProxy; + } + + public override IList ParseResponse(NetImportResponse importResponse) + { + var movies = new List(); + + if (!PreProcess(importResponse)) + { + return movies; + } + + var jsonResponse = JsonConvert.DeserializeObject(importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + foreach (var movie in jsonResponse.results) + { + // Movies with no Year Fix + if (string.IsNullOrWhiteSpace(movie.release_date)) + { + continue; + } + + movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); + } + + return movies; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListRequestGenerator.cs new file mode 100644 index 000000000..beb1bab1d --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListRequestGenerator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.TMDb.List +{ + public class TMDbListRequestGenerator : INetImportRequestGenerator + { + public TMDbListSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public IHttpRequestBuilderFactory RequestBuilder { get; set; } + public Logger Logger { get; set; } + + public TMDbListRequestGenerator() + { + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMoviesRequest()); + + return pageableRequests; + } + + private IEnumerable GetMoviesRequest() + { + Logger.Info($"Importing TMDb movies from list: {Settings.ListId}"); + + var requestBuilder = RequestBuilder.Create() + .SetSegment("api", "4") + .SetSegment("route", "list") + .SetSegment("id", Settings.ListId) + .SetSegment("secondaryRoute", ""); + + yield return new NetImportRequest(requestBuilder.Accept(HttpAccept.Json) + .Build()); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListSettings.cs new file mode 100644 index 000000000..823a3e3ed --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/List/TMDbListSettings.cs @@ -0,0 +1,27 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb.List +{ + public class TMDbListSettingsValidator : TMDbSettingsBaseValidator + { + public TMDbListSettingsValidator() + : base() + { + RuleFor(c => c.ListId).NotEmpty(); + } + } + + public class TMDbListSettings : TMDbSettingsBase + { + protected override AbstractValidator Validator => new TMDbListSettingsValidator(); + + public TMDbListSettings() + { + ListId = ""; + } + + [FieldDefinition(1, Label = "ListId", Type = FieldType.Textbox, HelpText = "TMDb Id of List to Follow")] + public string ListId { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonImport.cs b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonImport.cs new file mode 100644 index 000000000..3d1af7ea1 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonImport.cs @@ -0,0 +1,42 @@ +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb.Person +{ + public class TMDbPersonImport : TMDbNetImportBase + { + public TMDbPersonImport(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie searchForNewMovie, + Logger logger) + : base(requestBuilder, httpClient, configService, parsingService, searchForNewMovie, logger) + { + } + + public override string Name => "TMDb Person"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public override IParseNetImportResponse GetParser() + { + return new TMDbPersonParser(Settings, _skyhookProxy); + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbPersonRequestGenerator() + { + RequestBuilder = _requestBuilder, + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonParser.cs b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonParser.cs new file mode 100644 index 000000000..519613995 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonParser.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.NetImport.TMDb.Person +{ + public class TMDbPersonParser : TMDbParser + { + private readonly TMDbPersonSettings _settings; + private readonly ISearchForNewMovie _skyhookProxy; + + public TMDbPersonParser(TMDbPersonSettings settings, ISearchForNewMovie skyhookProxy) + : base(skyhookProxy) + { + _settings = settings; + _skyhookProxy = skyhookProxy; + } + + public override IList ParseResponse(NetImportResponse importResponse) + { + var movies = new List(); + + if (!PreProcess(importResponse)) + { + return movies; + } + + var jsonResponse = JsonConvert.DeserializeObject(importResponse.Content); + + // no movies were return + if (jsonResponse == null) + { + return movies; + } + + var crewTypes = GetCrewDepartments(); + + if (_settings.PersonCast) + { + foreach (var movie in jsonResponse.cast) + { + // Movies with no Year Fix + if (string.IsNullOrWhiteSpace(movie.release_date)) + { + continue; + } + + movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); + } + } + + if (crewTypes.Count > 0) + { + foreach (var movie in jsonResponse.crew) + { + // Movies with no Year Fix + if (string.IsNullOrWhiteSpace(movie.release_date)) + { + continue; + } + + if (crewTypes.Contains(movie.department)) + { + movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); + } + } + } + + return movies; + } + + private List GetCrewDepartments() + { + var creditsDepartment = new List(); + + if (_settings.PersonCastDirector) + { + creditsDepartment.Add("Directing"); + } + + if (_settings.PersonCastProducer) + { + creditsDepartment.Add("Production"); + } + + if (_settings.PersonCastSound) + { + creditsDepartment.Add("Sound"); + } + + if (_settings.PersonCastWriting) + { + creditsDepartment.Add("Writing"); + } + + return creditsDepartment; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonRequestGenerator.cs new file mode 100644 index 000000000..e240f3fe4 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonRequestGenerator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.TMDb.Person +{ + public class TMDbPersonRequestGenerator : INetImportRequestGenerator + { + public TMDbPersonSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public IHttpRequestBuilderFactory RequestBuilder { get; set; } + public Logger Logger { get; set; } + + public TMDbPersonRequestGenerator() + { + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMoviesRequest()); + + return pageableRequests; + } + + private IEnumerable GetMoviesRequest() + { + Logger.Info($"Importing TMDb movies from person: {Settings.PersonId}"); + + var requestBuilder = RequestBuilder.Create() + .SetSegment("api", "3") + .SetSegment("route", "person") + .SetSegment("id", Settings.PersonId) + .SetSegment("secondaryRoute", "/movie_credits"); + + yield return new NetImportRequest(requestBuilder.Accept(HttpAccept.Json) + .Build()); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonSettings.cs new file mode 100644 index 000000000..aa67fe7b8 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Person/TMDbPersonSettings.cs @@ -0,0 +1,43 @@ +using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb.Person +{ + public class TMDbPersonSettingsValidator : TMDbSettingsBaseValidator + { + public TMDbPersonSettingsValidator() + : base() + { + RuleFor(c => c.PersonId).Matches(@"^[1-9][0-9]*$", RegexOptions.IgnoreCase); + } + } + + public class TMDbPersonSettings : TMDbSettingsBase + { + protected override AbstractValidator Validator => new TMDbPersonSettingsValidator(); + + public TMDbPersonSettings() + { + PersonId = ""; + } + + [FieldDefinition(1, Label = "PersonId", Type = FieldType.Textbox, HelpText = "TMDb Id of Person to Follow")] + public string PersonId { get; set; } + + [FieldDefinition(2, Label = "Person Cast", HelpText = "Select if you want to include Cast credits", Type = FieldType.Checkbox)] + public bool PersonCast { get; set; } + + [FieldDefinition(3, Label = "Person Director Credits", HelpText = "Select if you want to include Director credits", Type = FieldType.Checkbox)] + public bool PersonCastDirector { get; set; } + + [FieldDefinition(4, Label = "Person Producer Credits", HelpText = "Select if you want to include Producer credits", Type = FieldType.Checkbox)] + public bool PersonCastProducer { get; set; } + + [FieldDefinition(5, Label = "Person Sound Credits", HelpText = "Select if you want to include Sound credits", Type = FieldType.Checkbox)] + public bool PersonCastSound { get; set; } + + [FieldDefinition(6, Label = "Person Writing Credits", HelpText = "Select if you want to include Writing credits", Type = FieldType.Checkbox)] + public bool PersonCastWriting { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularImport.cs b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularImport.cs new file mode 100644 index 000000000..20a352308 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularImport.cs @@ -0,0 +1,42 @@ +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb.Popular +{ + public class TMDbPopularImport : TMDbNetImportBase + { + public TMDbPopularImport(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie searchForNewMovie, + Logger logger) + : base(requestBuilder, httpClient, configService, parsingService, searchForNewMovie, logger) + { + } + + public override string Name => "TMDb Popular"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public override IParseNetImportResponse GetParser() + { + return new TMDbParser(_skyhookProxy); + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbPopularRequestGenerator() + { + RequestBuilder = _requestBuilder, + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularListType.cs similarity index 71% rename from src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs rename to src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularListType.cs index 27b18d2e8..2b99953b2 100644 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs +++ b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularListType.cs @@ -1,11 +1,9 @@ using System.Runtime.Serialization; -namespace NzbDrone.Core.NetImport.TMDb +namespace NzbDrone.Core.NetImport.TMDb.Popular { - public enum TMDbListType + public enum TMDbPopularListType { - [EnumMember(Value = "List")] - List = 0, [EnumMember(Value = "In Theaters")] Theaters = 1, [EnumMember(Value = "Popular")] diff --git a/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularRequestGenerator.cs new file mode 100644 index 000000000..2fdfd9ad9 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularRequestGenerator.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.TMDb.Popular +{ + public class TMDbPopularRequestGenerator : INetImportRequestGenerator + { + public TMDbPopularSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public IHttpRequestBuilderFactory RequestBuilder { get; set; } + public Logger Logger { get; set; } + + public int MaxPages { get; set; } + + public TMDbPopularRequestGenerator() + { + MaxPages = 3; + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMoviesRequests()); + + return pageableRequests; + } + + private IEnumerable GetMoviesRequests() + { + var minVoteCount = Settings.FilterCriteria.MinVotes; + var minVoteAverage = Settings.FilterCriteria.MinVoteAverage; + var ceritification = Settings.FilterCriteria.Ceritification; + var includeGenreIds = Settings.FilterCriteria.IncludeGenreIds; + var excludeGenreIds = Settings.FilterCriteria.ExcludeGenreIds; + var languageCode = (TMDbLanguageCodes)Settings.FilterCriteria.LanguageCode; + + var todaysDate = DateTime.Now.ToString("yyyy-MM-dd"); + var threeMonthsAgo = DateTime.Parse(todaysDate).AddMonths(-3).ToString("yyyy-MM-dd"); + var threeMonthsFromNow = DateTime.Parse(todaysDate).AddMonths(3).ToString("yyyy-MM-dd"); + + var requestBuilder = RequestBuilder.Create() + .SetSegment("api", "3") + .SetSegment("route", "discover") + .SetSegment("id", "") + .SetSegment("secondaryRoute", "movie"); + + switch (Settings.ListType) + { + case (int)TMDbPopularListType.Theaters: + requestBuilder.AddQueryParam("primary_release.gte", threeMonthsAgo) + .AddQueryParam("primary_release_date.lte", todaysDate); + break; + case (int)TMDbPopularListType.Popular: + requestBuilder.AddQueryParam("sort_by", "popularity.desc"); + break; + case (int)TMDbPopularListType.Top: + requestBuilder.AddQueryParam("sort_by", "vote_average.desc"); + break; + case (int)TMDbPopularListType.Upcoming: + requestBuilder.AddQueryParam("primary_release.gte", todaysDate) + .AddQueryParam("primary_release_date.lte", threeMonthsFromNow); + break; + } + + if (ceritification.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddQueryParam("certification", ceritification) + .AddQueryParam("certification_country", "US"); + } + + requestBuilder + .AddQueryParam("vote_count.gte", minVoteCount) + .AddQueryParam("vote_average.gte", minVoteAverage) + .AddQueryParam("with_genres", includeGenreIds) + .AddQueryParam("without_genres", excludeGenreIds) + .AddQueryParam("with_original_language", languageCode) + .Accept(HttpAccept.Json); + + for (var pageNumber = 1; pageNumber <= MaxPages; pageNumber++) + { + Logger.Info($"Importing TMDb movies from: {requestBuilder.BaseUrl}&page={pageNumber}"); + + requestBuilder.AddQueryParam("page", pageNumber, true); + + yield return new NetImportRequest(requestBuilder.Build()); + } + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularSettings.cs new file mode 100644 index 000000000..6462a70d8 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/Popular/TMDbPopularSettings.cs @@ -0,0 +1,32 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb.Popular +{ + public class TMDbPopularSettingsValidator : TMDbSettingsBaseValidator + { + public TMDbPopularSettingsValidator() + : base() + { + RuleFor(c => c.ListType).NotEmpty(); + + RuleFor(c => c.FilterCriteria).SetValidator(_ => new TMDbFilterSettingsValidator()); + } + } + + public class TMDbPopularSettings : TMDbSettingsBase + { + protected override AbstractValidator Validator => new TMDbPopularSettingsValidator(); + + public TMDbPopularSettings() + { + ListType = (int)TMDbPopularListType.Popular; + } + + [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TMDbPopularListType), HelpText = "Type of list your seeking to import from")] + public int ListType { get; set; } + + [FieldDefinition(2)] + public TMDbFilterSettings FilterCriteria { get; } = new TMDbFilterSettings(); + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbFilterSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbFilterSettings.cs new file mode 100644 index 000000000..d08db2a08 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbFilterSettings.cs @@ -0,0 +1,73 @@ +using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public class TMDbFilterSettingsValidator : AbstractValidator + { + public TMDbFilterSettingsValidator() + { + // Range 0.0 - 10.0 + RuleFor(c => c.MinVoteAverage) + .Matches(@"^(?!0\d)\d*(\.\d{1})?$", RegexOptions.IgnoreCase) + .When(c => c.MinVoteAverage.IsNotNullOrWhiteSpace()) + .WithMessage("Minimum vote average must be between 0 and 10"); + + // Greater than 0 + RuleFor(c => c.MinVotes) + .Matches(@"^[1-9][0-9]*$", RegexOptions.IgnoreCase) + .When(c => c.MinVotes.IsNotNullOrWhiteSpace()) + .WithMessage("Minimum votes must be greater than 0"); + + // Any valid certification + RuleFor(c => c.Ceritification) + .Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase) + .When(c => c.Ceritification.IsNotNullOrWhiteSpace()) + .WithMessage("Not a valid certification"); + + // CSV of numbers + RuleFor(c => c.IncludeGenreIds) + .Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase) + .When(c => c.IncludeGenreIds.IsNotNullOrWhiteSpace()) + .WithMessage("Genre Ids must be comma (,) or pipe (|) separated number ids"); + + // CSV of numbers + RuleFor(c => c.ExcludeGenreIds) + .Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase) + .When(c => c.ExcludeGenreIds.IsNotNullOrWhiteSpace()) + .WithMessage("Genre Ids must be comma (,) or pipe (|) separated number ids"); + } + } + + public class TMDbFilterSettings + { + public TMDbFilterSettings() + { + MinVoteAverage = "5"; + MinVotes = "1"; + LanguageCode = (int)TMDbLanguageCodes.en; + ExcludeGenreIds = ""; + IncludeGenreIds = ""; + } + + [FieldDefinition(1, Label = "Minimum Vote Average", HelpText = "Filter movies by votes (0.0-10.0)")] + public string MinVoteAverage { get; set; } + + [FieldDefinition(2, Label = "Minimum Number of Votes", HelpText = "Filter movies by number of votes")] + public string MinVotes { get; set; } + + [FieldDefinition(3, Label = "Certification", HelpText = "Filter movies by a single ceritification (NR,G,PG,PG-13,R,NC-17)")] + public string Ceritification { get; set; } + + [FieldDefinition(4, Label = "Include Genre Ids", HelpText = "Filter movies by TMDb Genre Ids (Comma Separated)")] + public string IncludeGenreIds { get; set; } + + [FieldDefinition(5, Label = "Exclude Genre Ids", HelpText = "Filter movies by TMDb Genre Ids (Comma Separated)")] + public string ExcludeGenreIds { get; set; } + + [FieldDefinition(6, Label = "Original Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter by Language")] + public int LanguageCode { get; set; } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs deleted file mode 100644 index bf31fe7a5..000000000 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs +++ /dev/null @@ -1,44 +0,0 @@ -using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.MetadataSource; -using NzbDrone.Core.Parser; - -namespace NzbDrone.Core.NetImport.TMDb -{ - public class TMDbImport : HttpNetImportBase - { - public override string Name => "TMDb Lists"; - - public override NetImportType ListType => NetImportType.TMDB; - public override bool Enabled => true; - public override bool EnableAuto => false; - - private readonly ISearchForNewMovie _skyhookProxy; - - public TMDbImport(IHttpClient httpClient, - IConfigService configService, - IParsingService parsingService, - ISearchForNewMovie skyhookProxy, - Logger logger) - : base(httpClient, configService, parsingService, logger) - { - _skyhookProxy = skyhookProxy; - } - - public override INetImportRequestGenerator GetRequestGenerator() - { - return new TMDbRequestGenerator() - { - Settings = Settings, - Logger = _logger, - HttpClient = _httpClient - }; - } - - public override IParseNetImportResponse GetParser() - { - return new TMDbParser(Settings, _skyhookProxy); - } - } -} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbImportBase.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbImportBase.cs new file mode 100644 index 000000000..36875a403 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbImportBase.cs @@ -0,0 +1,30 @@ +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb +{ + public abstract class TMDbNetImportBase : HttpNetImportBase + where TSettings : TMDbSettingsBase, new() + { + public override NetImportType ListType => NetImportType.TMDB; + + public readonly ISearchForNewMovie _skyhookProxy; + public readonly IHttpRequestBuilderFactory _requestBuilder; + + protected TMDbNetImportBase(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie skyhookProxy, + Logger logger) + : base(httpClient, configService, parsingService, logger) + { + _skyhookProxy = skyhookProxy; + _requestBuilder = requestBuilder.TMDB; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs index 0f904df73..264444c08 100644 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs @@ -4,87 +4,56 @@ using Newtonsoft.Json; using NzbDrone.Common.Extensions; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.Movies; using NzbDrone.Core.NetImport.Exceptions; namespace NzbDrone.Core.NetImport.TMDb { public class TMDbParser : IParseNetImportResponse { - private readonly TMDbSettings _settings; private readonly ISearchForNewMovie _skyhookProxy; - private NetImportResponse _importResponse; - public TMDbParser(TMDbSettings settings, ISearchForNewMovie skyhookProxy) + public TMDbParser(ISearchForNewMovie skyhookProxy) { _skyhookProxy = skyhookProxy; - _settings = settings; } - public IList ParseResponse(NetImportResponse importResponse) + public virtual IList ParseResponse(NetImportResponse importResponse) { - _importResponse = importResponse; + var movies = new List(); - var movies = new List(); - - if (!PreProcess(_importResponse)) + if (!PreProcess(importResponse)) { return movies; } - if (_settings.ListType != (int)TMDbListType.List) - { - var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); - - // no movies were return - if (jsonResponse == null) - { - return movies; - } + var jsonResponse = JsonConvert.DeserializeObject(importResponse.Content); - return jsonResponse.results.SelectList(_skyhookProxy.MapMovie); - } - else + // no movies were return + if (jsonResponse == null) { - var jsonResponse = JsonConvert.DeserializeObject(_importResponse.Content); - - // no movies were return - if (jsonResponse == null) - { - return movies; - } - - foreach (var movie in jsonResponse.items) - { - // Skip non-movie things - if (movie.media_type != "movie") - { - continue; - } - - // Movies with no Year Fix - if (string.IsNullOrWhiteSpace(movie.release_date)) - { - continue; - } - - movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); - } + return movies; } - return movies; + return jsonResponse.results.SelectList(_skyhookProxy.MapMovie); } - protected virtual bool PreProcess(NetImportResponse indexerResponse) + protected virtual bool PreProcess(NetImportResponse listResponse) { - if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + if (listResponse.HttpResponse.StatusCode != HttpStatusCode.OK) { - throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + throw new NetImportException(listResponse, + "TMDb API call resulted in an unexpected StatusCode [{0}]", + listResponse.HttpResponse.StatusCode); } - if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") && - indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json")) + if (listResponse.HttpResponse.Headers.ContentType != null && + listResponse.HttpResponse.Headers.ContentType.Contains("text/json") && + listResponse.HttpRequest.Headers.Accept != null && + !listResponse.HttpRequest.Headers.Accept.Contains("text/json")) { - throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable."); + throw new NetImportException(listResponse, + "TMDb responded with html content. Site is likely blocked or unavailable."); } return true; diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs deleted file mode 100644 index bba6cb3b0..000000000 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Http; -using NzbDrone.Common.Serializer; -using NzbDrone.Core.MetadataSource.SkyHook.Resource; - -namespace NzbDrone.Core.NetImport.TMDb -{ - public class TMDbRequestGenerator : INetImportRequestGenerator - { - public TMDbSettings Settings { get; set; } - public IHttpClient HttpClient { get; set; } - public Logger Logger { get; set; } - - public int MaxPages { get; set; } - - public TMDbRequestGenerator() - { - MaxPages = 3; - } - - public virtual NetImportPageableRequestChain GetMovies() - { - var minVoteCount = Settings.MinVotes; - var minVoteAverage = Settings.MinVoteAverage; - var ceritification = Settings.Ceritification; - var includeGenreIds = Settings.IncludeGenreIds; - var excludeGenreIds = Settings.ExcludeGenreIds; - var languageCode = (TMDbLanguageCodes)Settings.LanguageCode; - - var todaysDate = DateTime.Now.ToString("yyyy-MM-dd"); - var threeMonthsAgo = DateTime.Parse(todaysDate).AddMonths(-3).ToString("yyyy-MM-dd"); - var threeMonthsFromNow = DateTime.Parse(todaysDate).AddMonths(3).ToString("yyyy-MM-dd"); - - if (ceritification.IsNotNullOrWhiteSpace()) - { - ceritification = $"&certification_country=US&certification={ceritification}"; - } - - var tmdbParams = ""; - switch (Settings.ListType) - { - case (int)TMDbListType.List: - tmdbParams = $"/3/list/{Settings.ListId}?api_key=1a7373301961d03f97f853a876dd1212"; - break; - case (int)TMDbListType.Theaters: - tmdbParams = $"/3/discover/movie?api_key=1a7373301961d03f97f853a876dd1212&primary_release_date.gte={threeMonthsAgo}&primary_release_date.lte={todaysDate}&vote_count.gte={minVoteCount}&vote_average.gte={minVoteAverage}{ceritification}&with_genres={includeGenreIds}&without_genres={excludeGenreIds}&with_original_language={languageCode}"; - break; - case (int)TMDbListType.Popular: - tmdbParams = $"/3/discover/movie?api_key=1a7373301961d03f97f853a876dd1212&sort_by=popularity.desc&vote_count.gte={minVoteCount}&vote_average.gte={minVoteAverage}{ceritification}&with_genres={includeGenreIds}&without_genres={excludeGenreIds}&with_original_language={languageCode}"; - break; - case (int)TMDbListType.Top: - tmdbParams = $"/3/discover/movie?api_key=1a7373301961d03f97f853a876dd1212&sort_by=vote_average.desc&vote_count.gte={minVoteCount}&vote_average.gte={minVoteAverage}{ceritification}&with_genres={includeGenreIds}&without_genres={excludeGenreIds}&with_original_language={languageCode}"; - break; - case (int)TMDbListType.Upcoming: - tmdbParams = $"/3/discover/movie?api_key=1a7373301961d03f97f853a876dd1212&primary_release_date.gte={todaysDate}&primary_release_date.lte={threeMonthsFromNow}&vote_count.gte={minVoteCount}&vote_average.gte={minVoteAverage}{ceritification}&with_genres={includeGenreIds}&without_genres={excludeGenreIds}&with_original_language={languageCode}"; - break; - } - - var pageableRequests = new NetImportPageableRequestChain(); - if (Settings.ListType != (int)TMDbListType.List) - { - // First query to get the total_Pages - var requestBuilder = new HttpRequestBuilder($"{Settings.Link.TrimEnd("/")}") - { - LogResponseContent = true - }; - - requestBuilder.Method = HttpMethod.GET; - requestBuilder.Resource(tmdbParams); - - var request = requestBuilder - - // .AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212") - .Accept(HttpAccept.Json) - .Build(); - - var response = HttpClient.Execute(request); - var result = Json.Deserialize(response.Content); - - // @TODO Prolly some error handling to do here - pageableRequests.Add(GetMovies(tmdbParams, result.total_pages)); - return pageableRequests; - } - - pageableRequests.Add(GetMovies(tmdbParams, 0)); - return pageableRequests; - } - - private IEnumerable GetMovies(string tmdbParams, int totalPages) - { - var baseUrl = $"{Settings.Link.TrimEnd("/")}{tmdbParams}"; - if (Settings.ListType != (int)TMDbListType.List) - { - for (var pageNumber = 1; pageNumber <= totalPages; pageNumber++) - { - // Limit the amount of pages - if (pageNumber >= MaxPages + 1) - { - Logger.Info( - $"Found more than {MaxPages} pages, skipping the {totalPages - (MaxPages + 1)} remaining pages"); - break; - } - - Logger.Info($"Importing TMDb movies from: {baseUrl}&page={pageNumber}"); - yield return new NetImportRequest($"{baseUrl}&page={pageNumber}", HttpAccept.Json); - } - } - else - { - Logger.Info($"Importing TMDb movies from: {baseUrl}"); - yield return new NetImportRequest($"{baseUrl}", HttpAccept.Json); - } - } - } -} diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs index e76c46a79..2236e6fa3 100644 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs @@ -1,99 +1,28 @@ -using System.Text.RegularExpressions; -using FluentValidation; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Annotations; +using FluentValidation; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; namespace NzbDrone.Core.NetImport.TMDb { - public class TMDbSettingsValidator : AbstractValidator + public class TMDbSettingsBaseValidator : AbstractValidator + where TSettings : TMDbSettingsBase { - public TMDbSettingsValidator() + public TMDbSettingsBaseValidator() { - RuleFor(c => c.Link).ValidRootUrl(); - - // Greater than 0 - RuleFor(c => c.ListId) - .Matches(@"^[1-9][0-9]*$", RegexOptions.IgnoreCase) - .When(c => c.ListType == (int)TMDbListType.List) - .WithMessage("List Id is required when using TMDb Lists"); - - // Range 0.0 - 10.0 - RuleFor(c => c.MinVoteAverage) - .Matches(@"^(?!0\d)\d*(\.\d{1})?$", RegexOptions.IgnoreCase) - .When(c => c.MinVoteAverage.IsNotNullOrWhiteSpace()) - .WithMessage("Minimum vote average must be between 0 and 10"); - - // Greater than 0 - RuleFor(c => c.MinVotes) - .Matches(@"^[1-9][0-9]*$", RegexOptions.IgnoreCase) - .When(c => c.MinVotes.IsNotNullOrWhiteSpace()) - .WithMessage("Minimum votes must be greater than 0"); - - // Any valid certification - RuleFor(c => c.Ceritification) - .Matches(@"^\bNR\b|\bG\b|\bPG\b|\bPG\-13\b|\bR\b|\bNC\-17\b$", RegexOptions.IgnoreCase) - .When(c => c.Ceritification.IsNotNullOrWhiteSpace()) - .WithMessage("Not a valid certification"); - - // CSV of numbers - RuleFor(c => c.IncludeGenreIds) - .Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase) - .When(c => c.IncludeGenreIds.IsNotNullOrWhiteSpace()) - .WithMessage("Genre Ids must be comma (,) or pipe (|) separated number ids"); - - // CSV of numbers - RuleFor(c => c.ExcludeGenreIds) - .Matches(@"^\d+([,|]\d+)*$", RegexOptions.IgnoreCase) - .When(c => c.ExcludeGenreIds.IsNotNullOrWhiteSpace()) - .WithMessage("Genre Ids must be comma (,) or pipe (|) separated number ids"); } } - public class TMDbSettings : IProviderConfig + public class TMDbSettingsBase : IProviderConfig + where TSettings : TMDbSettingsBase { - private static readonly TMDbSettingsValidator Validator = new TMDbSettingsValidator(); - - public TMDbSettings() + protected virtual AbstractValidator Validator => new TMDbSettingsBaseValidator(); + public TMDbSettingsBase() { - Link = "https://api.themoviedb.org"; - ListType = (int)TMDbListType.Popular; - MinVoteAverage = "5"; - MinVotes = "1"; - LanguageCode = (int)TMDbLanguageCodes.en; } - [FieldDefinition(0, Label = "TMDb API URL", HelpText = "Link to to TMDb API URL, do not change unless you know what you are doing.")] - public string Link { get; set; } - - [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TMDbListType), HelpText = "Type of list your seeking to import from")] - public int ListType { get; set; } - - [FieldDefinition(2, Label = "Public List ID", HelpText = "Required for List (Ignores Filtering Options)")] - public string ListId { get; set; } - - [FieldDefinition(3, Label = "Minimum Vote Average", HelpText = "Filter movies by votes (0.0-10.0)")] - public string MinVoteAverage { get; set; } - - [FieldDefinition(4, Label = "Minimum Number of Votes", HelpText = "Filter movies by number of votes")] - public string MinVotes { get; set; } - - [FieldDefinition(5, Label = "Certification", HelpText = "Filter movies by a single ceritification (NR,G,PG,PG-13,R,NC-17)")] - public string Ceritification { get; set; } - - [FieldDefinition(6, Label = "Include Genre Ids", HelpText = "Filter movies by TMDb Genre Ids (Comma Separated)")] - public string IncludeGenreIds { get; set; } - - [FieldDefinition(7, Label = "Exclude Genre Ids", HelpText = "Filter movies by TMDb Genre Ids (Comma Separated)")] - public string ExcludeGenreIds { get; set; } - - [FieldDefinition(8, Label = "Original Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter by Language")] - public int LanguageCode { get; set; } - public NzbDroneValidationResult Validate() { - return new NzbDroneValidationResult(Validator.Validate(this)); + return new NzbDroneValidationResult(Validator.Validate((TSettings)this)); } } } diff --git a/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserImport.cs b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserImport.cs new file mode 100644 index 000000000..b7c734f4c --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserImport.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.NetImport.TMDb.User +{ + public class TMDbUserImport : TMDbNetImportBase + { + public TMDbUserImport(IRadarrCloudRequestBuilder requestBuilder, + IHttpClient httpClient, + IConfigService configService, + IParsingService parsingService, + ISearchForNewMovie searchForNewMovie, + Logger logger) + : base(requestBuilder, httpClient, configService, parsingService, searchForNewMovie, logger) + { + } + + public override string Name => "TMDb User"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + public override IParseNetImportResponse GetParser() + { + return new TMDbParser(_skyhookProxy); + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new TMDbUserRequestGenerator() + { + RequestBuilder = _requestBuilder, + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + + public override object RequestAction(string action, IDictionary query) + { + if (action == "startOAuth") + { + var requestBuilder = _requestBuilder.Create() + .SetSegment("api", "4") + .SetSegment("route", "auth") + .SetSegment("id", "") + .SetSegment("secondaryRoute", "request_token") + .AddQueryParam("redirect_to", query["callbackUrl"]); + + requestBuilder.Method = HttpMethod.POST; + + var request = requestBuilder.Build(); + + var response = Json.Deserialize(_httpClient.Execute(request).Content); + + var oAuthRequest = new HttpRequestBuilder(Settings.OAuthUrl) + .AddQueryParam("request_token", response.request_token) + .Build(); + + return new + { + OauthUrl = oAuthRequest.Url.ToString(), + RequestToken = response.request_token + }; + } + else if (action == "getOAuthToken") + { + var requestBuilder = _requestBuilder.Create() + .SetSegment("api", "4") + .SetSegment("route", "auth") + .SetSegment("id", "") + .SetSegment("secondaryRoute", "access_token") + .AddQueryParam("request_token", query["requestToken"]); + + requestBuilder.Method = HttpMethod.POST; + + var request = requestBuilder.Build(); + + var response = Json.Deserialize(_httpClient.Execute(request).Content); + + return new + { + accountId = response.account_id, + accessToken = response.access_token, + }; + } + + return new { }; + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserListType.cs b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserListType.cs new file mode 100644 index 000000000..07ee01cdb --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserListType.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; + +namespace NzbDrone.Core.NetImport.TMDb.User +{ + public enum TMDbUserListType + { + [EnumMember(Value = "Watchlist")] + Watchlist = 1, + [EnumMember(Value = "Recommendations")] + Recommendations = 2, + [EnumMember(Value = "Rated")] + Rated = 3, + [EnumMember(Value = "Favorite")] + Favorite = 4 + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserRequestGenerator.cs b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserRequestGenerator.cs new file mode 100644 index 000000000..10aaab253 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserRequestGenerator.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.TMDb.User +{ + public class TMDbUserRequestGenerator : INetImportRequestGenerator + { + public TMDbUserSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public IHttpRequestBuilderFactory RequestBuilder { get; set; } + public Logger Logger { get; set; } + + public int MaxPages { get; set; } + + public TMDbUserRequestGenerator() + { + MaxPages = 3; + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + pageableRequests.Add(GetMoviesRequests()); + + return pageableRequests; + } + + private IEnumerable GetMoviesRequests() + { + var requestBuilder = RequestBuilder.Create() + .SetHeader("Authorization", $"Bearer {Settings.AccessToken}") + .SetSegment("api", "4") + .SetSegment("route", "account") + .SetSegment("id", Settings.AccountId); + + switch (Settings.ListType) + { + case (int)TMDbUserListType.Watchlist: + requestBuilder.SetSegment("secondaryRoute", "/movie/watchlist"); + break; + case (int)TMDbUserListType.Recommendations: + requestBuilder.SetSegment("secondaryRoute", "/movie/recommendations"); + break; + case (int)TMDbUserListType.Rated: + requestBuilder.SetSegment("secondaryRoute", "/movie/rated"); + break; + case (int)TMDbUserListType.Favorite: + requestBuilder.SetSegment("secondaryRoute", "/movie/favorites"); + break; + } + + requestBuilder.Accept(HttpAccept.Json); + + requestBuilder.Method = HttpMethod.GET; + + yield return new NetImportRequest(requestBuilder.Build()); + } + } +} diff --git a/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserSettings.cs b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserSettings.cs new file mode 100644 index 000000000..8e708e8f6 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/TMDb/User/TMDbUserSettings.cs @@ -0,0 +1,40 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.NetImport.TMDb.User +{ + public class TMDbUserSettingsValidator : TMDbSettingsBaseValidator + { + public TMDbUserSettingsValidator() + : base() + { + RuleFor(c => c.ListType).NotEmpty(); + RuleFor(c => c.AccessToken).NotEmpty(); + RuleFor(c => c.AccountId).NotEmpty(); + } + } + + public class TMDbUserSettings : TMDbSettingsBase + { + protected override AbstractValidator Validator => new TMDbUserSettingsValidator(); + + public TMDbUserSettings() + { + ListType = (int)TMDbUserListType.Watchlist; + } + + public string OAuthUrl => "https://www.themoviedb.org/auth/access"; + + [FieldDefinition(0, Label = "Account Id", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)] + public string AccountId { get; set; } + + [FieldDefinition(0, Label = "Access Token", Type = FieldType.Textbox, Hidden = HiddenType.Hidden)] + public string AccessToken { get; set; } + + [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(TMDbUserListType), HelpText = "Type of list your seeking to import from")] + public int ListType { get; set; } + + [FieldDefinition(99, Label = "Authenticate with TMDB", Type = FieldType.OAuth)] + public string SignIn { get; set; } + } +} diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj index dfb708ea9..364c3990d 100644 --- a/src/NzbDrone.Core/Radarr.Core.csproj +++ b/src/NzbDrone.Core/Radarr.Core.csproj @@ -28,17 +28,17 @@ - + Resources\Logo\64.png - + - + diff --git a/src/Radarr.Api.V3/Movies/FetchMovieListModule.cs b/src/Radarr.Api.V3/Movies/FetchMovieListModule.cs index 8fd4615d5..243556dd3 100644 --- a/src/Radarr.Api.V3/Movies/FetchMovieListModule.cs +++ b/src/Radarr.Api.V3/Movies/FetchMovieListModule.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Nancy; +using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Movies; @@ -30,7 +31,12 @@ namespace Radarr.Api.V3.Movies foreach (var movie in results) { - var mapped = _movieSearch.MapMovieToTmdbMovie(movie); + var mapped = movie; + + if (movie.TmdbId == 0 || !movie.Images.Any() || movie.Overview.IsNullOrWhiteSpace()) + { + mapped = _movieSearch.MapMovieToTmdbMovie(movie); + } if (mapped != null) {