From accf8a9efa24740147b2be735da5d0bf3e160d20 Mon Sep 17 00:00:00 2001 From: Leonardo Galli Date: Tue, 6 Jun 2017 22:40:44 +0200 Subject: [PATCH] Added: Ability to see TMDB and lists going through the Radarr API on the discovery page. Added: More lists (specifically presets for IMDB Top 250 and IMDB Popular) Added: Ability to set Radarr API endpoint as list. --- .../Series/MovieDiscoverModule.cs | 21 ++++- .../NetImport/RSSImportFixture.cs | 2 +- .../Download/DownloadClientBase.cs | 5 +- .../Extras/Metadata/MetadataBase.cs | 5 +- src/NzbDrone.Core/Indexers/IndexerBase.cs | 23 +++-- src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 33 ++++---- src/NzbDrone.Core/Indexers/Torznab/Torznab.cs | 9 +- .../MetadataSource/ISearchForNewMovie.cs | 2 + .../RadarrAPI/RadarrAPIClient.cs | 11 ++- .../SkyHook/Resource/TMDBResources.cs | 16 +--- .../MetadataSource/SkyHook/SkyHookProxy.cs | 2 +- src/NzbDrone.Core/NetImport/NetImportBase.cs | 28 +++---- .../NetImport/NetImportFactory.cs | 9 ++ .../NetImport/RSSImport/RSSImport.cs | 45 +++++----- .../NetImport/Radarr/RadarrParser.cs | 78 +++++++++++++++++ .../NetImport/Radarr/RadarrProxied.cs | 84 +++++++++++++++++++ .../Radarr/RadarrRequestGenerator.cs | 38 +++++++++ .../NetImport/Radarr/RadarrSettings.cs | 42 ++++++++++ .../NetImport/TMDb/TMDbImport.cs | 7 +- .../NetImport/TMDb/TMDbParser.cs | 31 ++----- .../Notifications/NotificationBase.cs | 5 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 5 ++ src/NzbDrone.Core/ThingiProvider/IProvider.cs | 4 +- .../ThingiProvider/ProviderFactory.cs | 4 +- src/UI/AddMovies/AddMoviesView.js | 60 ++++++++++++- src/UI/AddMovies/AddMoviesViewTemplate.hbs | 7 ++ .../AddMovies/DiscoverableListDropdownView.js | 13 +++ .../DiscoverableListDropdownViewTemplate.hbs | 3 + .../AddMovies/List/AddFromListCollection.js | 1 + src/UI/AddMovies/addMovies.less | 22 ++++- 30 files changed, 478 insertions(+), 137 deletions(-) create mode 100644 src/NzbDrone.Core/NetImport/Radarr/RadarrParser.cs create mode 100644 src/NzbDrone.Core/NetImport/Radarr/RadarrProxied.cs create mode 100644 src/NzbDrone.Core/NetImport/Radarr/RadarrRequestGenerator.cs create mode 100644 src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs create mode 100644 src/UI/AddMovies/DiscoverableListDropdownView.js create mode 100644 src/UI/AddMovies/DiscoverableListDropdownViewTemplate.hbs diff --git a/src/NzbDrone.Api/Series/MovieDiscoverModule.cs b/src/NzbDrone.Api/Series/MovieDiscoverModule.cs index 7d6400d5c..6670eab67 100644 --- a/src/NzbDrone.Api/Series/MovieDiscoverModule.cs +++ b/src/NzbDrone.Api/Series/MovieDiscoverModule.cs @@ -6,17 +6,22 @@ using NzbDrone.Core.MetadataSource; using System.Linq; using System; using NzbDrone.Api.REST; +using NzbDrone.Core.NetImport; +using NzbDrone.Api.NetImport; namespace NzbDrone.Api.Movie { public class MovieDiscoverModule : NzbDroneRestModule { private readonly IDiscoverNewMovies _searchProxy; + private readonly INetImportFactory _netImportFactory; - public MovieDiscoverModule(IDiscoverNewMovies searchProxy) + public MovieDiscoverModule(IDiscoverNewMovies searchProxy, INetImportFactory netImportFactory) : base("/movies/discover") { _searchProxy = searchProxy; + _netImportFactory = netImportFactory; + Get["/lists"] = x => GetLists(); Get["/{action?recommendations}"] = x => Search(x.action); } @@ -26,6 +31,20 @@ namespace NzbDrone.Api.Movie return MapToResource(imdbResults).AsResponse(); } + private Response GetLists() + { + var lists = _netImportFactory.Discoverable(); + + return lists.Select(definition => { + var resource = new NetImportResource(); + resource.Id = definition.Definition.Id; + + resource.Name = definition.Definition.Name; + + return resource; + }).AsResponse(); + } + private static IEnumerable MapToResource(IEnumerable movies) { foreach (var currentSeries in movies) diff --git a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs index de13c40bf..a3da351c8 100644 --- a/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs +++ b/src/NzbDrone.Core.Test/NetImport/RSSImportFixture.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.NetImport [SetUp] public void Setup() { - Subject.Definition = Subject.DefaultDefinitions.First(); + Subject.Definition = Subject.GetDefaultDefinitions().First(); } private void GivenRecentFeedResponse(string rssXmlFile) { diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index 84d12227d..e7ccef71f 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -28,7 +28,10 @@ namespace NzbDrone.Core.Download public virtual ProviderMessage Message => null; - public IEnumerable DefaultDefinitions => new List(); + public IEnumerable GetDefaultDefinitions() + { + return new List(); + } public ProviderDefinition Definition { get; set; } diff --git a/src/NzbDrone.Core/Extras/Metadata/MetadataBase.cs b/src/NzbDrone.Core/Extras/Metadata/MetadataBase.cs index f60928703..7f05cbe16 100644 --- a/src/NzbDrone.Core/Extras/Metadata/MetadataBase.cs +++ b/src/NzbDrone.Core/Extras/Metadata/MetadataBase.cs @@ -17,7 +17,10 @@ namespace NzbDrone.Core.Extras.Metadata public virtual ProviderMessage Message => null; - public IEnumerable DefaultDefinitions => new List(); + public IEnumerable GetDefaultDefinitions() + { + return new List(); + } public ProviderDefinition Definition { get; set; } diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 95fda4871..8cd1c68e0 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -38,21 +38,18 @@ namespace NzbDrone.Core.Indexers public virtual ProviderMessage Message => null; - public virtual IEnumerable DefaultDefinitions + public virtual IEnumerable GetDefaultDefinitions() { - get + var config = (IProviderConfig)new TSettings(); + + yield return new IndexerDefinition { - var config = (IProviderConfig)new TSettings(); - - yield return new IndexerDefinition - { - Name = GetType().Name, - EnableRss = config.Validate().IsValid && SupportsRss, - EnableSearch = config.Validate().IsValid && SupportsSearch, - Implementation = GetType().Name, - Settings = config - }; - } + Name = GetType().Name, + EnableRss = config.Validate().IsValid && SupportsRss, + EnableSearch = config.Validate().IsValid && SupportsSearch, + Implementation = GetType().Name, + Settings = config + }; } public virtual ProviderDefinition Definition { get; set; } diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 078a8f3de..d24aa5b20 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -35,25 +35,22 @@ namespace NzbDrone.Core.Indexers.Newznab return new NewznabRssParser(Settings); } - public override IEnumerable DefaultDefinitions + public override IEnumerable GetDefaultDefinitions() { - get - { - yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr")); - yield return GetDefinition("DrunkenSlug", GetSettings("https://api.drunkenslug.com")); - yield return GetDefinition("Nzb-Tortuga", GetSettings("https://www.nzb-tortuga.com")); - yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su")); - yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat")); - yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws")); - yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info")); - yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net")); - yield return GetDefinition("Nzbs.org", GetSettings("http://nzbs.org")); - yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me")); - yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com")); - yield return GetDefinition("PFmonkey", GetSettings("https://www.pfmonkey.com")); - yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com")); - yield return GetDefinition("Usenet Crawler", GetSettings("https://www.usenet-crawler.com")); - } + yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr")); + yield return GetDefinition("DrunkenSlug", GetSettings("https://api.drunkenslug.com")); + yield return GetDefinition("Nzb-Tortuga", GetSettings("https://www.nzb-tortuga.com")); + yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su")); + yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat")); + yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws")); + yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info")); + yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net")); + yield return GetDefinition("Nzbs.org", GetSettings("http://nzbs.org")); + yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me")); + yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com")); + yield return GetDefinition("PFmonkey", GetSettings("https://www.pfmonkey.com")); + yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com")); + yield return GetDefinition("Usenet Crawler", GetSettings("https://www.usenet-crawler.com")); } public Newznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs index 51539e304..19d2e3111 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs @@ -37,13 +37,10 @@ namespace NzbDrone.Core.Indexers.Torznab return new TorznabRssParser(); } - public override IEnumerable DefaultDefinitions + public override IEnumerable GetDefaultDefinitions() { - get - { - yield return GetDefinition("Jackett", GetSettings("http://localhost:9117/torznab/YOURINDEXER")); - yield return GetDefinition("HD4Free.xyz", GetSettings("http://hd4free.xyz")); - } + yield return GetDefinition("Jackett", GetSettings("http://localhost:9117/torznab/YOURINDEXER")); + yield return GetDefinition("HD4Free.xyz", GetSettings("http://hd4free.xyz")); } public Torznab(INewznabCapabilitiesProvider capabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) diff --git a/src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs b/src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs index bda58e7af..2a16b88af 100644 --- a/src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs +++ b/src/NzbDrone.Core/MetadataSource/ISearchForNewMovie.cs @@ -8,5 +8,7 @@ namespace NzbDrone.Core.MetadataSource List SearchForNewMovie(string title); Movie MapMovieToTmdbMovie(Movie movie); + + Movie MapMovie(SkyHook.Resource.MovieResult result); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/RadarrAPI/RadarrAPIClient.cs b/src/NzbDrone.Core/MetadataSource/RadarrAPI/RadarrAPIClient.cs index 49bdce34b..3f6ebdd62 100644 --- a/src/NzbDrone.Core/MetadataSource/RadarrAPI/RadarrAPIClient.cs +++ b/src/NzbDrone.Core/MetadataSource/RadarrAPI/RadarrAPIClient.cs @@ -11,27 +11,30 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI { IHttpRequestBuilderFactory RadarrAPI { get; } List DiscoverMovies(string action, Func enhanceRequest); + string APIURL { get; } } public class RadarrAPIClient : IRadarrAPIClient { private readonly IHttpClient _httpClient; + public string APIURL { get; private set; } + public RadarrAPIClient(IConfigFileProvider configFile, IHttpClient httpClient) { _httpClient = httpClient; if (configFile.Branch == "nightly") { - RadarrAPI = new HttpRequestBuilder("https://staging.api.radarr.video/{route}/{action}") - .CreateFactory(); + APIURL = "https://staging.api.radarr.video"; } else { - RadarrAPI = new HttpRequestBuilder("https://api.radarr.video/v2/{route}/{action}") - .CreateFactory(); + APIURL = "https://api.radarr.video/v2"; } + RadarrAPI = new HttpRequestBuilder(APIURL+"/{route}/{action}") + .CreateFactory(); } private HttpResponse Execute(HttpRequest request) diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs index 05bd960dc..c485d5e58 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TMDBResources.cs @@ -168,23 +168,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public object poster_path { get; set; } } - public class Item + public class Item : MovieResult { - public string poster_path { get; set; } - public bool adult { get; set; } - public string overview { get; set; } - public string release_date { get; set; } - public string original_title { get; set; } - public int[] genre_ids { get; set; } - public int id { get; set; } public string media_type { get; set; } - public string original_language { get; set; } - public string title { get; set; } - public string backdrop_path { get; set; } - public float popularity { get; set; } - public int vote_count { get; set; } - public bool video { get; set; } - public float vote_average { get; set; } public string first_air_date { get; set; } public string[] origin_country { get; set; } public string name { get; set; } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 0bb3edf36..f6db9011f 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -546,7 +546,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - private Movie MapMovie(MovieResult result) + public Movie MapMovie(MovieResult result) { var imdbMovie = new Movie(); imdbMovie.TmdbId = result.id; diff --git a/src/NzbDrone.Core/NetImport/NetImportBase.cs b/src/NzbDrone.Core/NetImport/NetImportBase.cs index 29174f7c8..007818c06 100644 --- a/src/NzbDrone.Core/NetImport/NetImportBase.cs +++ b/src/NzbDrone.Core/NetImport/NetImportBase.cs @@ -32,23 +32,21 @@ namespace NzbDrone.Core.NetImport public Type ConfigContract => typeof(TSettings); public virtual ProviderMessage Message => null; - public virtual IEnumerable DefaultDefinitions + + public virtual IEnumerable GetDefaultDefinitions() { - get + var config = (IProviderConfig)new TSettings(); + + yield return new NetImportDefinition { - var config = (IProviderConfig)new TSettings(); - - yield return new NetImportDefinition - { - Name = this.Name, - Enabled = config.Validate().IsValid && Enabled, - EnableAuto = true, - ProfileId = 1, - MinimumAvailability = MovieStatusType.Announced, - Implementation = GetType().Name, - Settings = config - }; - } + Name = this.Name, + Enabled = config.Validate().IsValid && Enabled, + EnableAuto = true, + ProfileId = 1, + MinimumAvailability = MovieStatusType.Announced, + Implementation = GetType().Name, + Settings = config + }; } public virtual ProviderDefinition Definition { get; set; } diff --git a/src/NzbDrone.Core/NetImport/NetImportFactory.cs b/src/NzbDrone.Core/NetImport/NetImportFactory.cs index 19a8eadee..51f3cbd2a 100644 --- a/src/NzbDrone.Core/NetImport/NetImportFactory.cs +++ b/src/NzbDrone.Core/NetImport/NetImportFactory.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.NetImport public interface INetImportFactory : IProviderFactory { List Enabled(); + + List Discoverable(); } public class NetImportFactory : ProviderFactory, INetImportFactory @@ -46,6 +48,13 @@ namespace NzbDrone.Core.NetImport return indexers.ToList(); } + public List Discoverable() + { + var enabledImporters = GetAvailableProviders().Where(n => (n.GetType() == typeof(Radarr.RadarrProxied) || n.GetType() == typeof(TMDb.TMDbImport))); + var indexers = FilterBlockedIndexers(enabledImporters); + return indexers.ToList(); + } + private IEnumerable FilterBlockedIndexers(IEnumerable importers) { foreach (var importer in importers) diff --git a/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs index 9b562b33f..f64a8c5fa 100644 --- a/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs +++ b/src/NzbDrone.Core/NetImport/RSSImport/RSSImport.cs @@ -17,33 +17,30 @@ namespace NzbDrone.Core.NetImport.RSSImport : base(httpClient, configService, parsingService, logger) { } - public override IEnumerable DefaultDefinitions + public override IEnumerable GetDefaultDefinitions() { - get + foreach (var def in base.GetDefaultDefinitions()) { - foreach (var def in base.DefaultDefinitions) - { - yield return def; - } - yield return new NetImportDefinition - { - Name = "IMDb List", - Enabled = Enabled, - EnableAuto = true, - ProfileId = 1, - Implementation = GetType().Name, - Settings = new RSSImportSettings { Link = "http://rss.imdb.com/list/YOURLISTID" }, - }; - yield return new NetImportDefinition - { - Name = "IMDb Watchlist", - Enabled = Enabled, - EnableAuto = true, - ProfileId = 1, - Implementation = GetType().Name, - Settings = new RSSImportSettings { Link = "http://rss.imdb.com/user/IMDBUSERID/watchlist" }, - }; + yield return def; } + yield return new NetImportDefinition + { + Name = "IMDb List", + Enabled = Enabled, + EnableAuto = true, + ProfileId = 1, + Implementation = GetType().Name, + Settings = new RSSImportSettings { Link = "http://rss.imdb.com/list/YOURLISTID" }, + }; + yield return new NetImportDefinition + { + Name = "IMDb Watchlist", + Enabled = Enabled, + EnableAuto = true, + ProfileId = 1, + Implementation = GetType().Name, + Settings = new RSSImportSettings { Link = "http://rss.imdb.com/user/IMDBUSERID/watchlist" }, + }; } public override INetImportRequestGenerator GetRequestGenerator() diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrParser.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrParser.cs new file mode 100644 index 000000000..18cbdd1af --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrParser.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; +using NzbDrone.Core.NetImport.Exceptions; +using System; +using System.Collections.Generic; +using System.Net; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.RadarrAPI; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.NetImport.Radarr +{ + public class RadarrParser : IParseNetImportResponse + { + private readonly RadarrSettings _settings; + private NetImportResponse _importResponse; + private readonly ISearchForNewMovie _skyhookProxy; + private readonly Logger _logger; + + public RadarrParser(RadarrSettings settings, ISearchForNewMovie skyhookProxy) + { + _skyhookProxy = skyhookProxy;//TinyIoC.TinyIoCContainer.Current.Resolve(); + _settings = settings; + } + + public IList ParseResponse(NetImportResponse importResponse) + { + _importResponse = 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; + } + + return jsonResponse.SelectList(_skyhookProxy.MapMovie); + + + } + + protected virtual bool PreProcess(NetImportResponse indexerResponse) + { + try + { + var error = JsonConvert.DeserializeObject(indexerResponse.HttpResponse.Content); + + if (error != null && error.Errors != null && error.Errors.Count != 0) + { + throw new RadarrAPIException(error); + } + } + catch (JsonSerializationException) + { + //No error! + } + + + if (indexerResponse.HttpResponse.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new HttpException(indexerResponse.HttpRequest, indexerResponse.HttpResponse); + } + + return true; + } + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrProxied.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrProxied.cs new file mode 100644 index 000000000..cb1be66a6 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrProxied.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.MetadataSource; + + +namespace NzbDrone.Core.NetImport.Radarr +{ + public class RadarrProxied : HttpNetImportBase + { + public override string Name => "Radarr Proxied Lists"; + public override bool Enabled => true; + public override bool EnableAuto => false; + + private readonly IHttpClient _httpClient; + private readonly Logger _logger; + private readonly ISearchForNewMovie _skyhookProxy; + + public RadarrProxied(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, ISearchForNewMovie skyhookProxy, + Logger logger) + : base(httpClient, configService, parsingService, logger) + { + _skyhookProxy = skyhookProxy; + _logger = logger; + _httpClient = httpClient; + } + + public override IEnumerable GetDefaultDefinitions() + { + foreach (var def in base.GetDefaultDefinitions()) + { + yield return def; + } + + yield return new NetImportDefinition + { + Name = "IMDb Top 250", + Enabled = Enabled, + EnableAuto = true, + ProfileId = 1, + Implementation = GetType().Name, + Settings = new RadarrSettings { Path = "/imdb/top250" }, + }; + yield return new NetImportDefinition + { + Name = "IMDb Popular Movies", + Enabled = Enabled, + EnableAuto = true, + ProfileId = 1, + Implementation = GetType().Name, + Settings = new RadarrSettings { Path = "/imdb/popular" }, + }; + yield return new NetImportDefinition + { + Name = "IMDb List", + Enabled = Enabled, + EnableAuto = true, + ProfileId = 1, + Implementation = GetType().Name, + Settings = new RadarrSettings { Path = "/imdb/list?listId=LISTID" }, + }; + + + } + + public override INetImportRequestGenerator GetRequestGenerator() + { + return new RadarrRequestGenerator() + { + Settings = Settings, + Logger = _logger, + HttpClient = _httpClient + }; + } + + public override IParseNetImportResponse GetParser() + { + return new RadarrParser(Settings, _skyhookProxy); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrRequestGenerator.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrRequestGenerator.cs new file mode 100644 index 000000000..2c8c0d493 --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrRequestGenerator.cs @@ -0,0 +1,38 @@ +using System; +using NzbDrone.Common.Http; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.MetadataSource.SkyHook.Resource; + +namespace NzbDrone.Core.NetImport.Radarr +{ + public class RadarrRequestGenerator : INetImportRequestGenerator + { + public RadarrSettings Settings { get; set; } + public IHttpClient HttpClient { get; set; } + public Logger Logger { get; set; } + + public int MaxPages { get; set; } + + public RadarrRequestGenerator() + { + MaxPages = 3; + } + + public virtual NetImportPageableRequestChain GetMovies() + { + var pageableRequests = new NetImportPageableRequestChain(); + + var baseUrl = $"{Settings.APIURL.TrimEnd("/")}"; + + var request = new NetImportRequest($"{baseUrl}{Settings.Path}", HttpAccept.Json); + + request.HttpRequest.SuppressHttpError = true; + + pageableRequests.Add(new List { request }); + return pageableRequests; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs b/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs new file mode 100644 index 000000000..edc69d6ee --- /dev/null +++ b/src/NzbDrone.Core/NetImport/Radarr/RadarrSettings.cs @@ -0,0 +1,42 @@ +using FluentValidation; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; +using System.Text.RegularExpressions; +using NzbDrone.Core.MetadataSource.RadarrAPI; + +namespace NzbDrone.Core.NetImport.Radarr +{ + + public class RadarrSettingsValidator : AbstractValidator + { + public RadarrSettingsValidator() + { + RuleFor(c => c.APIURL).ValidRootUrl(); + } + } + + public class RadarrSettings : IProviderConfig + { + private static readonly RadarrSettingsValidator Validator = new RadarrSettingsValidator(); + + public RadarrSettings() + { + APIURL = "https://api.radarr.video"; + Path = ""; + } + + [FieldDefinition(0, Label = "Radarr API URL", HelpText = "Link to to Radarr API URL.Use https://staging.api.radarr.video if you are on nightly.")] + public string APIURL { get; set; } + + [FieldDefinition(1, Label = "Path to list", HelpText = "Path to the list proxied by the Radarr API. Check the wiki for available lists.")] + public string Path { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } + +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs index 1524e307c..d6ef58f7e 100644 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs @@ -1,6 +1,7 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; +using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Parser; @@ -14,13 +15,15 @@ namespace NzbDrone.Core.NetImport.TMDb private readonly IHttpClient _httpClient; private readonly Logger _logger; + private readonly ISearchForNewMovie _skyhookProxy; - public TMDbImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, + public TMDbImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, ISearchForNewMovie skyhookProxy, Logger logger) : base(httpClient, configService, parsingService, logger) { _logger = logger; _httpClient = httpClient; + _skyhookProxy = skyhookProxy; } public override INetImportRequestGenerator GetRequestGenerator() @@ -35,7 +38,7 @@ namespace NzbDrone.Core.NetImport.TMDb public override IParseNetImportResponse GetParser() { - return new TMDbParser(Settings); + return new TMDbParser(Settings, _skyhookProxy); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs index beb8ad013..59ed165e5 100644 --- a/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs +++ b/src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs @@ -6,6 +6,8 @@ using System.Net; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.MetadataSource.SkyHook.Resource; +using NzbDrone.Core.MetadataSource; +using TinyIoC; namespace NzbDrone.Core.NetImport.TMDb { @@ -13,10 +15,12 @@ namespace NzbDrone.Core.NetImport.TMDb { private readonly TMDbSettings _settings; private NetImportResponse _importResponse; + private readonly ISearchForNewMovie _skyhookProxy; private readonly Logger _logger; - public TMDbParser(TMDbSettings settings) + public TMDbParser(TMDbSettings settings, ISearchForNewMovie skyhookProxy) { + _skyhookProxy = skyhookProxy; _settings = settings; } @@ -41,22 +45,7 @@ namespace NzbDrone.Core.NetImport.TMDb return movies; } - foreach (var movie in jsonResponse.results) - { - // Movies with no Year Fix - if (string.IsNullOrWhiteSpace(movie.release_date)) - { - continue; - } - - movies.AddIfNotNull(new Tv.Movie() - { - Title = movie.title, - TmdbId = movie.id, - ImdbId = null, - Year = DateTime.Parse(movie.release_date).Year - }); - } + return jsonResponse.results.SelectList(_skyhookProxy.MapMovie); } else { @@ -82,13 +71,7 @@ namespace NzbDrone.Core.NetImport.TMDb continue; } - movies.AddIfNotNull(new Tv.Movie() - { - Title = movie.title, - TmdbId = movie.id, - ImdbId = null, - Year = DateTime.Parse(movie.release_date).Year - }); + movies.AddIfNotNull(_skyhookProxy.MapMovie(movie)); } } diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index c6a415cda..70129c2ed 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -14,7 +14,10 @@ namespace NzbDrone.Core.Notifications public virtual ProviderMessage Message => null; - public IEnumerable DefaultDefinitions => new List(); + public IEnumerable GetDefaultDefinitions() + { + return new List(); + } public ProviderDefinition Definition { get; set; } public abstract ValidationResult Test(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 28a7fad20..532038f52 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1285,6 +1285,10 @@ + + + + @@ -1358,6 +1362,7 @@ + diff --git a/src/NzbDrone.Core/ThingiProvider/IProvider.cs b/src/NzbDrone.Core/ThingiProvider/IProvider.cs index 386d2bfaf..d93e61f81 100644 --- a/src/NzbDrone.Core/ThingiProvider/IProvider.cs +++ b/src/NzbDrone.Core/ThingiProvider/IProvider.cs @@ -9,7 +9,9 @@ namespace NzbDrone.Core.ThingiProvider string Name { get; } Type ConfigContract { get; } ProviderMessage Message { get; } - IEnumerable DefaultDefinitions { get; } + + IEnumerable GetDefaultDefinitions(); + ProviderDefinition Definition { get; set; } ValidationResult Test(); object RequestAction(string stage, IDictionary query); diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs index 74b77410d..70929efe4 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -43,7 +43,7 @@ namespace NzbDrone.Core.ThingiProvider { foreach (var provider in _providers) { - var definition = provider.DefaultDefinitions + var definition = provider.GetDefaultDefinitions() .OfType() .FirstOrDefault(v => v.Name == null || v.Name == provider.Name); @@ -68,7 +68,7 @@ namespace NzbDrone.Core.ThingiProvider { var provider = _providers.First(v => v.GetType().Name == providerDefinition.Implementation); - var definitions = provider.DefaultDefinitions + var definitions = provider.GetDefaultDefinitions() .OfType() .Where(v => v.Name != null && v.Name != provider.Name) .ToList(); diff --git a/src/UI/AddMovies/AddMoviesView.js b/src/UI/AddMovies/AddMoviesView.js index 57ec4288b..138271a00 100644 --- a/src/UI/AddMovies/AddMoviesView.js +++ b/src/UI/AddMovies/AddMoviesView.js @@ -3,7 +3,12 @@ var $ = require('jquery'); var vent = require('vent'); var Marionette = require('marionette'); var AddMoviesCollection = require('./AddMoviesCollection'); +var AddFromListCollection = require('./List/AddFromListCollection'); var SearchResultCollectionView = require('./SearchResultCollectionView'); +var DiscoverableListDropdownView = require("./DiscoverableListDropdownView"); +var DiscoverableListCollection = require("./DiscoverableListCollection"); +var DiscoverableListCollectionView = require("./DiscoverableListCollectionView"); +var DiscoverMoviesCollection = require("./DiscoverMoviesCollection"); var EmptyView = require('./EmptyView'); var NotFoundView = require('./NotFoundView'); var DiscoverEmptyView = require('./DiscoverEmptyView'); @@ -15,7 +20,8 @@ module.exports = Marionette.Layout.extend({ template : 'AddMovies/AddMoviesViewTemplate', regions : { - searchResult : '#search-result' + myRegion : '#my-region', + searchResult : '#search-result', }, ui : { @@ -26,14 +32,17 @@ module.exports = Marionette.Layout.extend({ discoverBefore : ".x-discover-before", discoverRecos : ".x-recommendations-tab", discoverPopular : ".x-popular-tab" , - discoverUpcoming : ".x-upcoming-tab" + discoverUpcoming : ".x-upcoming-tab", + discoverLists : ".x-lists-tab" }, events : { 'click .x-load-more' : '_onLoadMore', "click .x-recommendations-tab" : "_discoverRecos", "click .x-popular-tab" : "_discoverPopular", - "click .x-upcoming-tab" : "_discoverUpcoming" + "click .x-upcoming-tab" : "_discoverUpcoming", + "click .x-lists-tab" : "_discoverLists", + "click .discoverable-list-item" : "_discoverList" }, initialize : function(options) { @@ -58,6 +67,15 @@ module.exports = Marionette.Layout.extend({ isExisting : this.isExisting }); + /*this.listsDropdown = new DiscoverableListCollectionView({ + collection : DiscoverableListCollection + });*/ + + this.listenTo(DiscoverableListCollection, 'sync', this._showListDropdown); + this.listsDropdown = new DiscoverableListCollectionView({ + collection : DiscoverableListCollection + }) + this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); if (options.action === "search") { @@ -125,6 +143,8 @@ module.exports = Marionette.Layout.extend({ this.ui.moviesSearch.focus(); this.ui.loadMore.hide(); + this._showListDropdown(); + if (this.isDiscover) { this.ui.discoverBefore.show(); } @@ -208,6 +228,14 @@ module.exports = Marionette.Layout.extend({ } }, + _showListDropdown : function() { + this.listsDropdown = new DiscoverableListDropdownView(DiscoverableListCollection.toJSON()); + this.listsDropdown.render(); + $("#list-dropdown").html(this.listsDropdown.$el.html()); + //debugger; + //this.myRegion.show(new DiscoverableListDropdownView(DiscoverableListCollection.toJSON())); + }, + _abortExistingSearch : function() { if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) { console.log('aborting previous pending search request.'); @@ -229,7 +257,14 @@ module.exports = Marionette.Layout.extend({ if (this.collection.action === action) { return } - this.collection.reset(); + + if (this.collection.specialProperty === "special") { + this.collection.reset(); + this.collection = new DiscoverMoviesCollection(); + this.resultCollectionView.collection = this.collection; + } + + this.listenTo(this.collection, 'sync', this._showResults); this.searchResult.show(new LoadingView()); this.collection.action = action; this.currentSearchPromise = this.collection.fetch(); @@ -253,5 +288,22 @@ module.exports = Marionette.Layout.extend({ this._discover("upcoming"); }, + _discoverLists : function() { + /*this.ui.discoverLists.tab("show"); + this.ui.discoverHeader.html("");*/ + }, + + _discoverList : function(options) { + this.ui.discoverLists.tab("show"); + this.ui.discoverHeader.html("Showing movies from list: "+options.target.textContent); + + this.collection.reset(); + this.collection = new AddFromListCollection(); + this.listenTo(this.collection, 'sync', this._showResults); + this.searchResult.show(new LoadingView()); + this.currentSearchPromise = this.collection.fetch({ data: { listId: options.target.value } }); + this.resultCollectionView.collection = this.collection; + } + }); diff --git a/src/UI/AddMovies/AddMoviesViewTemplate.hbs b/src/UI/AddMovies/AddMoviesViewTemplate.hbs index 6fb4e54b2..4e41c6afb 100644 --- a/src/UI/AddMovies/AddMoviesViewTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesViewTemplate.hbs @@ -10,6 +10,13 @@
  • Recommendations
  • Popular
  • Upcoming
  • +

    Recommendations by The Movie Database based on your library: diff --git a/src/UI/AddMovies/DiscoverableListDropdownView.js b/src/UI/AddMovies/DiscoverableListDropdownView.js new file mode 100644 index 000000000..3c98f247c --- /dev/null +++ b/src/UI/AddMovies/DiscoverableListDropdownView.js @@ -0,0 +1,13 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddMovies/DiscoverableListDropdownViewTemplate', + + initialize : function(lists) { + this.lists = lists; + }, + + templateHelpers : function() { + return this.lists; + } +}); diff --git a/src/UI/AddMovies/DiscoverableListDropdownViewTemplate.hbs b/src/UI/AddMovies/DiscoverableListDropdownViewTemplate.hbs new file mode 100644 index 000000000..28d7c2e01 --- /dev/null +++ b/src/UI/AddMovies/DiscoverableListDropdownViewTemplate.hbs @@ -0,0 +1,3 @@ +{{#each this}} +
  • {{name}} +{{/each}} diff --git a/src/UI/AddMovies/List/AddFromListCollection.js b/src/UI/AddMovies/List/AddFromListCollection.js index 12f5cb7f0..7e2ae369d 100644 --- a/src/UI/AddMovies/List/AddFromListCollection.js +++ b/src/UI/AddMovies/List/AddFromListCollection.js @@ -5,6 +5,7 @@ var _ = require('underscore'); module.exports = Backbone.Collection.extend({ url : window.NzbDrone.ApiRoot + '/netimport/movies', model : MovieModel, + specialProperty: "special", parse : function(response) { var self = this; diff --git a/src/UI/AddMovies/addMovies.less b/src/UI/AddMovies/addMovies.less index db5f790e7..b69cd618b 100644 --- a/src/UI/AddMovies/addMovies.less +++ b/src/UI/AddMovies/addMovies.less @@ -6,9 +6,9 @@ } .page-size { - display: inline-block; - width: 200px; - float: right; + display: inline-block; + width: 200px; + float: right; margin-top: 8px; } @@ -148,6 +148,22 @@ } } +#list-dropdown { + width: 100%; +} + +.discoverable-list-item { + font-size: 14px; + padding-top: 5px; + padding-left: 15px; + padding-right: 15px; + padding-bottom: 5px; +} + +.discoverable-list-item:hover { + background-color: rgb(237, 237, 237); +} + li.add-new { .clickable;