diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 9347015db..01e5fcb23 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -270,6 +270,7 @@ + diff --git a/src/NzbDrone.Api/Series/MovieDiscoverModule.cs b/src/NzbDrone.Api/Series/MovieDiscoverModule.cs new file mode 100644 index 000000000..e99de8ecd --- /dev/null +++ b/src/NzbDrone.Api/Series/MovieDiscoverModule.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Nancy; +using NzbDrone.Api.Extensions; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource; +using System.Linq; +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Movie +{ + public class MovieDiscoverModule : NzbDroneRestModule + { + private readonly IDiscoverNewMovies _searchProxy; + + public MovieDiscoverModule(IDiscoverNewMovies searchProxy) + : base("/movies/discover") + { + _searchProxy = searchProxy; + Get["/"] = x => Search(); + } + + private Response Search() + { + var imdbResults = _searchProxy.DiscoverNewMovies(); + return MapToResource(imdbResults).AsResponse(); + } + + private static IEnumerable MapToResource(IEnumerable movies) + { + foreach (var currentSeries in movies) + { + var resource = currentSeries.ToResource(); + var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); + if (poster != null) + { + resource.RemotePoster = poster.Url; + } + + yield return resource; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/IDiscoverNewMovies.cs b/src/NzbDrone.Core/MetadataSource/IDiscoverNewMovies.cs new file mode 100644 index 000000000..4a04e47d9 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/IDiscoverNewMovies.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.MetadataSource +{ + public interface IDiscoverNewMovies + { + List DiscoverNewMovies(); + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 02736d214..36afb7252 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -18,7 +18,7 @@ using NzbDrone.Core.Profiles; namespace NzbDrone.Core.MetadataSource.SkyHook { - public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMovieInfo, ISearchForNewMovie + public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries, IProvideMovieInfo, ISearchForNewMovie, IDiscoverNewMovies { private readonly IHttpClient _httpClient; private readonly Logger _logger; @@ -348,6 +348,29 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return resources.movie_results.SelectList(MapMovie).FirstOrDefault(); } + public List DiscoverNewMovies() + { + string allIds = string.Join(",", _movieService.GetAllMovies().Select(m => m.TmdbId)); + var request = new HttpRequestBuilder("https://radarr.video/recommendations/api.php").AddQueryParam("tmdbids", allIds).Build(); + + request.AllowAutoRedirect = true; + + var response = _httpClient.Get>(request); + if (response.StatusCode != HttpStatusCode.OK) + { + throw new HttpException(request, response); + } + + if (response.Headers.ContentType != HttpAccept.Json.Value) + { + throw new HttpException(request, response); + } + + var movieResults = response.Resource; + + return movieResults.SelectList(MapMovie); + } + private string StripTrailingTheFromTitle(string title) { if(title.EndsWith(",the")) @@ -551,6 +574,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook imdbMovie.Images = new List(); imdbMovie.Overview = result.overview; + imdbMovie.Ratings = new Ratings { Value = (decimal)result.vote_average, Votes = result.vote_count}; + try { var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 850e06687..62f95b9fd 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1279,6 +1279,7 @@ + diff --git a/src/UI/AddMovies/AddMoviesLayout.js b/src/UI/AddMovies/AddMoviesLayout.js index 46fe8aa65..5065da16f 100644 --- a/src/UI/AddMovies/AddMoviesLayout.js +++ b/src/UI/AddMovies/AddMoviesLayout.js @@ -8,6 +8,7 @@ var ProfileCollection = require('../Profile/ProfileCollection'); var AddFromListView = require("./List/AddFromListView"); var RootFolderCollection = require('./RootFolders/RootFolderCollection'); var BulkImportView = require("./BulkImport/BulkImportView"); +var DiscoverMoviesCollection = require("./DiscoverMoviesCollection"); require('../Movies/MoviesCollection'); module.exports = Marionette.Layout.extend({ @@ -22,7 +23,7 @@ module.exports = Marionette.Layout.extend({ }, events : { - 'click .x-import' : '_importMovies', + 'click .x-discover' : '_discoverMovies', 'click .x-bulk-import' : '_bulkImport', 'click .x-add-new' : '_addMovies', "click .x-add-lists" : "_addFromList", @@ -70,10 +71,11 @@ module.exports = Marionette.Layout.extend({ this.workspace.show(new BulkImportView({ model : options.model})); }, - _importMovies : function() { - this.rootFolderLayout = new RootFolderLayout(); - this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); - AppLayout.modalRegion.show(this.rootFolderLayout); + _discoverMovies : function(options) { + options = options || {}; + options.action = "discover"; + options.collection = new DiscoverMoviesCollection(); + this.workspace.show(new AddMoviesView(options)); }, _addMovies : function(options) { diff --git a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs index ba9224d8b..887b21e3e 100644 --- a/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesLayoutTemplate.hbs @@ -2,7 +2,7 @@
- +
diff --git a/src/UI/AddMovies/AddMoviesView.js b/src/UI/AddMovies/AddMoviesView.js index 7185d114f..51dda6128 100644 --- a/src/UI/AddMovies/AddMoviesView.js +++ b/src/UI/AddMovies/AddMoviesView.js @@ -1,4 +1,5 @@ var _ = require('underscore'); +var $ = require('jquery'); var vent = require('vent'); var Marionette = require('marionette'); var AddMoviesCollection = require('./AddMoviesCollection'); @@ -7,6 +8,7 @@ var EmptyView = require('./EmptyView'); var NotFoundView = require('./NotFoundView'); var ErrorView = require('./ErrorView'); var LoadingView = require('../Shared/LoadingView'); +var FullMovieCollection = require("../Movies/FullMovieCollection"); module.exports = Marionette.Layout.extend({ template : 'AddMovies/AddMoviesViewTemplate', @@ -18,7 +20,8 @@ module.exports = Marionette.Layout.extend({ ui : { moviesSearch : '.x-movies-search', searchBar : '.x-search-bar', - loadMore : '.x-load-more' + loadMore : '.x-load-more', + discoverHeader : ".x-discover-header" }, events : { @@ -27,7 +30,7 @@ module.exports = Marionette.Layout.extend({ initialize : function(options) { this.isExisting = options.isExisting; - this.collection = new AddMoviesCollection(); + this.collection = options.collection || new AddMoviesCollection(); if (this.isExisting) { this.collection.unmappedFolderModel = this.model; @@ -51,6 +54,13 @@ module.exports = Marionette.Layout.extend({ if (options.action === "search") { this.search({term: options.query}); + } else if (options.action == "discover") { + this.isDiscover = true; + if (FullMovieCollection.length > 0) { + this._discover(); + } else { + this.listenTo(FullMovieCollection, "sync", this._discover); + } } }, @@ -58,6 +68,8 @@ module.exports = Marionette.Layout.extend({ onRender : function() { var self = this; + + this.$el.addClass(this.className); this.ui.moviesSearch.keyup(function(e) { @@ -95,10 +107,21 @@ module.exports = Marionette.Layout.extend({ if (this.isExisting) { this.ui.searchBar.hide(); } + + if (this.isDiscover) { + this.ui.searchBar.hide(); + if (this.collection.length == 0) { + this.searchResult.show(new LoadingView()); + } + } }, onShow : function() { + this.ui.discoverHeader.hide(); this.ui.moviesSearch.focus(); + if (this.isDiscover) { + this.ui.discoverHeader.show(); + } }, search : function(options) { @@ -140,7 +163,10 @@ module.exports = Marionette.Layout.extend({ _onLoadMore : function() { var showingAll = this.resultCollectionView.showMore(); - this.ui.searchBar.show(); + if (!this.isDiscover) { + this.ui.searchBar.show(); + } + if (showingAll) { this.ui.loadMore.hide(); @@ -185,5 +211,9 @@ module.exports = Marionette.Layout.extend({ this.searchResult.show(new ErrorView({ term : this.collection.term })); this.collection.term = ''; } + }, + + _discover : function() { + this.collection.fetch() } }); diff --git a/src/UI/AddMovies/AddMoviesViewTemplate.hbs b/src/UI/AddMovies/AddMoviesViewTemplate.hbs index 9f9e0660c..2fc612744 100644 --- a/src/UI/AddMovies/AddMoviesViewTemplate.hbs +++ b/src/UI/AddMovies/AddMoviesViewTemplate.hbs @@ -4,6 +4,9 @@ {{folder.path}}
{{/if}} +

+ Recommendations by The Movie Database based on your library: +

diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less index 51f8caab4..cbe72d3a1 100644 --- a/src/UI/Content/icons.less +++ b/src/UI/Content/icons.less @@ -307,6 +307,9 @@ .icon-sonarr-deleted { .fa-icon-content(@fa-var-trash); } +.icon-sonarr-star { + .fa-icon-content(@fa-var-star); +} .icon-sonarr-clear { .fa-icon-content(@fa-var-trash);