mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 06:45:19 -07:00
Feature/Add TMDb Functionality (#739)
* Inital TMDb List, needs paging support and user lists, private or public * Clean up Base * TMDb grabs upto 5 pages for import, update validation, added minimum vote average * Added logic for MovieLinksTemplate * Clean up a bit * Add Public Lists
This commit is contained in:
parent
c5bb259555
commit
1e28a2e5d4
12 changed files with 588 additions and 261 deletions
|
@ -150,4 +150,41 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
public string size { get; set; }
|
public string size { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 string iso_639_1 { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public object poster_path { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Item
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
public string original_name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ namespace NzbDrone.Core.NetImport
|
||||||
|
|
||||||
public override bool Enabled => true;
|
public override bool Enabled => true;
|
||||||
|
|
||||||
public bool SupportsPaging => PageSize > 0;
|
public bool SupportsPaging => PageSize > 20;
|
||||||
|
|
||||||
public virtual int PageSize => 0;
|
public virtual int PageSize => 20;
|
||||||
|
|
||||||
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
|
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
@ -58,13 +58,6 @@ namespace NzbDrone.Core.NetImport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fullyUpdated = false;
|
|
||||||
Movie lastMovie = null;
|
|
||||||
if (isRecent)
|
|
||||||
{
|
|
||||||
//lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < pageableRequestChain.Tiers; i++)
|
for (int i = 0; i < pageableRequestChain.Tiers; i++)
|
||||||
{
|
{
|
||||||
var pageableRequests = pageableRequestChain.GetTier(i);
|
var pageableRequests = pageableRequestChain.GetTier(i);
|
||||||
|
@ -80,37 +73,6 @@ namespace NzbDrone.Core.NetImport
|
||||||
var page = FetchPage(request, parser);
|
var page = FetchPage(request, parser);
|
||||||
|
|
||||||
pagedReleases.AddRange(page);
|
pagedReleases.AddRange(page);
|
||||||
|
|
||||||
if (isRecent && page.Any())
|
|
||||||
{
|
|
||||||
if (lastMovie == null)
|
|
||||||
{
|
|
||||||
fullyUpdated = true;
|
|
||||||
break;
|
|
||||||
}/*
|
|
||||||
var oldestReleaseDate = page.Select(v => v.PublishDate).Min();
|
|
||||||
if (oldestReleaseDate < lastReleaseInfo.PublishDate || page.Any(v => v.DownloadUrl == lastReleaseInfo.DownloadUrl))
|
|
||||||
{
|
|
||||||
fullyUpdated = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pagedReleases.Count >= MaxNumResultsPerQuery &&
|
|
||||||
oldestReleaseDate < DateTime.UtcNow - TimeSpan.FromHours(24))
|
|
||||||
{
|
|
||||||
fullyUpdated = false;
|
|
||||||
break;
|
|
||||||
}*///update later
|
|
||||||
}
|
|
||||||
else if (pagedReleases.Count >= MaxNumResultsPerQuery)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsFullPage(page))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
movies.AddRange(pagedReleases);
|
movies.AddRange(pagedReleases);
|
||||||
|
@ -121,29 +83,9 @@ namespace NzbDrone.Core.NetImport
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRecent && !movies.Empty())
|
|
||||||
{
|
|
||||||
var ordered = movies.OrderByDescending(v => v.Title).ToList();
|
|
||||||
|
|
||||||
lastMovie = ordered.First();
|
|
||||||
//_indexerStatusService.UpdateRssSyncStatus(Definition.Id, lastReleaseInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
//_indexerStatusService.RecordSuccess(Definition.Id);
|
|
||||||
}
|
}
|
||||||
catch (WebException webException)
|
catch (WebException webException)
|
||||||
{
|
{
|
||||||
if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
|
|
||||||
webException.Status == WebExceptionStatus.ConnectFailure)
|
|
||||||
{
|
|
||||||
//_indexerStatusService.RecordConnectionFailure(Definition.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||||
webException.Message.Contains("timed out"))
|
webException.Message.Contains("timed out"))
|
||||||
{
|
{
|
||||||
|
@ -158,28 +100,23 @@ namespace NzbDrone.Core.NetImport
|
||||||
{
|
{
|
||||||
if ((int)httpException.Response.StatusCode == 429)
|
if ((int)httpException.Response.StatusCode == 429)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
|
|
||||||
_logger.Warn("API Request Limit reached for {0}", this);
|
_logger.Warn("API Request Limit reached for {0}", this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
_logger.Warn("{0} {1}", this, httpException.Message);
|
_logger.Warn("{0} {1}", this, httpException.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (RequestLimitReachedException)
|
catch (RequestLimitReachedException)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
|
|
||||||
_logger.Warn("API Request Limit reached for {0}", this);
|
_logger.Warn("API Request Limit reached for {0}", this);
|
||||||
}
|
}
|
||||||
catch (ApiKeyException)
|
catch (ApiKeyException)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
_logger.Warn("Invalid API Key for {0} {1}", this, url);
|
_logger.Warn("Invalid API Key for {0} {1}", this, url);
|
||||||
}
|
}
|
||||||
catch (CloudFlareCaptchaException ex)
|
catch (CloudFlareCaptchaException ex)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
if (ex.IsExpired)
|
if (ex.IsExpired)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this);
|
_logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this);
|
||||||
|
@ -191,13 +128,11 @@ namespace NzbDrone.Core.NetImport
|
||||||
}
|
}
|
||||||
catch (IndexerException ex)
|
catch (IndexerException ex)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
var message = string.Format("{0} - {1}", ex.Message, url);
|
var message = string.Format("{0} - {1}", ex.Message, url);
|
||||||
_logger.Warn(ex, message);
|
_logger.Warn(ex, message);
|
||||||
}
|
}
|
||||||
catch (Exception feedEx)
|
catch (Exception feedEx)
|
||||||
{
|
{
|
||||||
//_indexerStatusService.RecordFailure(Definition.Id);
|
|
||||||
feedEx.Data.Add("FeedUrl", url);
|
feedEx.Data.Add("FeedUrl", url);
|
||||||
_logger.Error(feedEx, "An error occurred while processing feed. " + url);
|
_logger.Error(feedEx, "An error occurred while processing feed. " + url);
|
||||||
}
|
}
|
||||||
|
@ -205,11 +140,6 @@ namespace NzbDrone.Core.NetImport
|
||||||
return movies;
|
return movies;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual bool IsFullPage(IList<Movie> page)
|
|
||||||
{
|
|
||||||
return PageSize != 0 && page.Count >= PageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual IList<Movie> FetchPage(NetImportRequest request, IParseNetImportResponse parser)
|
protected virtual IList<Movie> FetchPage(NetImportRequest request, IParseNetImportResponse parser)
|
||||||
{
|
{
|
||||||
var response = FetchIndexerResponse(request);
|
var response = FetchIndexerResponse(request);
|
||||||
|
|
40
src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs
Normal file
40
src/NzbDrone.Core/NetImport/TMDb/TMDbImport.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.NetImport.TMDb
|
||||||
|
{
|
||||||
|
public class TMDbImport : HttpNetImportBase<TMDbSettings>
|
||||||
|
{
|
||||||
|
public override string Name => "TMDb Lists";
|
||||||
|
public override bool Enabled => true;
|
||||||
|
public override bool EnableAuto => false;
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public TMDbImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService,
|
||||||
|
Logger logger)
|
||||||
|
: base(httpClient, configService, parsingService, logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override INetImportRequestGenerator GetRequestGenerator()
|
||||||
|
{
|
||||||
|
return new TMDbRequestGenerator()
|
||||||
|
{
|
||||||
|
Settings = Settings,
|
||||||
|
Logger = _logger,
|
||||||
|
HttpClient = _httpClient
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IParseNetImportResponse GetParser()
|
||||||
|
{
|
||||||
|
return new TMDbParser(Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs
Normal file
28
src/NzbDrone.Core/NetImport/TMDb/TMDbLanguageCodes.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.NetImport.TMDb
|
||||||
|
{
|
||||||
|
public enum TMDbLanguageCodes
|
||||||
|
{
|
||||||
|
da,
|
||||||
|
nl,
|
||||||
|
en,
|
||||||
|
fi,
|
||||||
|
fr,
|
||||||
|
de,
|
||||||
|
el,
|
||||||
|
hu,
|
||||||
|
it,
|
||||||
|
ja,
|
||||||
|
ko,
|
||||||
|
no,
|
||||||
|
pl,
|
||||||
|
pt,
|
||||||
|
ru,
|
||||||
|
es,
|
||||||
|
sv,
|
||||||
|
tr,
|
||||||
|
vi,
|
||||||
|
zh
|
||||||
|
}
|
||||||
|
}
|
18
src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs
Normal file
18
src/NzbDrone.Core/NetImport/TMDb/TMDbListType.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.NetImport.TMDb
|
||||||
|
{
|
||||||
|
public enum TMDbListType
|
||||||
|
{
|
||||||
|
[EnumMember(Value = "List")]
|
||||||
|
List = 0,
|
||||||
|
[EnumMember(Value = "In Theaters")]
|
||||||
|
Theaters = 1,
|
||||||
|
[EnumMember(Value = "Popular")]
|
||||||
|
Popular = 2,
|
||||||
|
[EnumMember(Value = "Top Rated")]
|
||||||
|
Top = 3,
|
||||||
|
[EnumMember(Value = "Upcoming")]
|
||||||
|
Upcoming = 4
|
||||||
|
}
|
||||||
|
}
|
110
src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs
Normal file
110
src/NzbDrone.Core/NetImport/TMDb/TMDbParser.cs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.NetImport.TMDb
|
||||||
|
{
|
||||||
|
public class TMDbParser : IParseNetImportResponse
|
||||||
|
{
|
||||||
|
private readonly TMDbSettings _settings;
|
||||||
|
private NetImportResponse _importResponse;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public TMDbParser(TMDbSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<Tv.Movie> ParseResponse(NetImportResponse importResponse)
|
||||||
|
{
|
||||||
|
_importResponse = importResponse;
|
||||||
|
|
||||||
|
var movies = new List<Tv.Movie>();
|
||||||
|
|
||||||
|
if (!PreProcess(_importResponse))
|
||||||
|
{
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings.ListType != (int) TMDbListType.List)
|
||||||
|
{
|
||||||
|
var jsonResponse = JsonConvert.DeserializeObject<MovieSearchRoot>(_importResponse.Content);
|
||||||
|
|
||||||
|
// no movies were return
|
||||||
|
if (jsonResponse == null)
|
||||||
|
{
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var movie in jsonResponse.results)
|
||||||
|
{
|
||||||
|
if (movie.vote_average >= double.Parse(_settings.MinVoteAverage))
|
||||||
|
{
|
||||||
|
movies.AddIfNotNull(new Tv.Movie()
|
||||||
|
{
|
||||||
|
Title = movie.title,
|
||||||
|
TmdbId = movie.id,
|
||||||
|
ImdbId = null,
|
||||||
|
Year = DateTime.Parse(movie.release_date).Year
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var jsonResponse = JsonConvert.DeserializeObject<ListResponseRoot>(_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movie.vote_average >= double.Parse(_settings.MinVoteAverage))
|
||||||
|
{
|
||||||
|
movies.AddIfNotNull(new Tv.Movie()
|
||||||
|
{
|
||||||
|
Title = movie.title,
|
||||||
|
TmdbId = movie.id,
|
||||||
|
ImdbId = null,
|
||||||
|
Year = DateTime.Parse(movie.release_date).Year
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool PreProcess(NetImportResponse indexerResponse)
|
||||||
|
{
|
||||||
|
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.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"))
|
||||||
|
{
|
||||||
|
throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
104
src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs
Normal file
104
src/NzbDrone.Core/NetImport/TMDb/TMDbRequestGenerator.cs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NLog;
|
||||||
|
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 searchType = "";
|
||||||
|
|
||||||
|
switch (Settings.ListType)
|
||||||
|
{
|
||||||
|
case (int)TMDbListType.List:
|
||||||
|
searchType = $"/3/list/{Settings.ListId}";
|
||||||
|
break;
|
||||||
|
case (int)TMDbListType.Theaters:
|
||||||
|
searchType = "/3/movie/now_playing";
|
||||||
|
break;
|
||||||
|
case (int)TMDbListType.Popular:
|
||||||
|
searchType = "/3/movie/popular";
|
||||||
|
break;
|
||||||
|
case (int)TMDbListType.Top:
|
||||||
|
searchType = "/3/movie/top_rated";
|
||||||
|
break;
|
||||||
|
case (int)TMDbListType.Upcoming:
|
||||||
|
searchType = "/3/movie/upcoming";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageableRequests = new NetImportPageableRequestChain();
|
||||||
|
if (Settings.ListType != (int) TMDbListType.List)
|
||||||
|
{
|
||||||
|
// First query to get the total_Pages
|
||||||
|
var requestBuilder = new HttpRequestBuilder($"{Settings.Link.Trim()}")
|
||||||
|
{
|
||||||
|
LogResponseContent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
requestBuilder.Method = HttpMethod.GET;
|
||||||
|
requestBuilder.Resource(searchType);
|
||||||
|
|
||||||
|
var request = requestBuilder
|
||||||
|
.AddQueryParam("api_key", "1a7373301961d03f97f853a876dd1212")
|
||||||
|
.Accept(HttpAccept.Json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var response = HttpClient.Execute(request);
|
||||||
|
var result = Json.Deserialize<MovieSearchRoot>(response.Content);
|
||||||
|
|
||||||
|
// @TODO Prolly some error handling to do here
|
||||||
|
pageableRequests.Add(GetPagedRequests(searchType, result.total_pages));
|
||||||
|
return pageableRequests;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageableRequests.Add(GetPagedRequests(searchType, 0));
|
||||||
|
return pageableRequests;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<NetImportRequest> GetPagedRequests(string searchType, int totalPages)
|
||||||
|
{
|
||||||
|
var baseUrl = $"{Settings.Link.Trim()}{searchType}?api_key=1a7373301961d03f97f853a876dd1212";
|
||||||
|
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.Trace($"Importing TMDb movies from: {baseUrl}&page={pageNumber}");
|
||||||
|
yield return new NetImportRequest($"{baseUrl}&page={pageNumber}", HttpAccept.Json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Trace($"Importing TMDb movies from: {baseUrl}");
|
||||||
|
yield return new NetImportRequest($"{baseUrl}", HttpAccept.Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs
Normal file
55
src/NzbDrone.Core/NetImport/TMDb/TMDbSettings.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.NetImport.TMDb
|
||||||
|
{
|
||||||
|
|
||||||
|
public class TMDbSettingsValidator : AbstractValidator<TMDbSettings>
|
||||||
|
{
|
||||||
|
public TMDbSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Link).ValidRootUrl();
|
||||||
|
RuleFor(c => double.Parse(c.MinVoteAverage)).ExclusiveBetween(0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TMDbSettings : NetImportBaseSettings
|
||||||
|
{
|
||||||
|
private static readonly TMDbSettingsValidator Validator = new TMDbSettingsValidator();
|
||||||
|
|
||||||
|
public TMDbSettings()
|
||||||
|
{
|
||||||
|
Link = "https://api.themoviedb.org";
|
||||||
|
MinVoteAverage = "5.5";
|
||||||
|
// Language = (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 new 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 = "Language", Type = FieldType.Select, SelectOptions = typeof(TMDbLanguageCodes), HelpText = "Filter movies by Language")]
|
||||||
|
//public int Language { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Minimum Vote Average", HelpText = "Filter movies by rating (0.0-10.0)")]
|
||||||
|
public string MinVoteAverage { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Public List ID", HelpText = "Required for List")]
|
||||||
|
public string ListId { get; set; }
|
||||||
|
|
||||||
|
public new NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -127,6 +127,12 @@
|
||||||
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
|
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
|
||||||
<Compile Include="Datastore\Migration\128_remove_kickass.cs" />
|
<Compile Include="Datastore\Migration\128_remove_kickass.cs" />
|
||||||
<Compile Include="Datastore\Migration\130_remove_wombles_kickass.cs" />
|
<Compile Include="Datastore\Migration\130_remove_wombles_kickass.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbSettings.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbListType.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbImport.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbParser.cs" />
|
||||||
|
<Compile Include="NetImport\TMDb\TMDbRequestGenerator.cs" />
|
||||||
<Compile Include="NetImport\Trakt\TraktAPI.cs" />
|
<Compile Include="NetImport\Trakt\TraktAPI.cs" />
|
||||||
<Compile Include="NetImport\Trakt\TraktImport.cs" />
|
<Compile Include="NetImport\Trakt\TraktImport.cs" />
|
||||||
<Compile Include="NetImport\Trakt\TraktListType.cs" />
|
<Compile Include="NetImport\Trakt\TraktListType.cs" />
|
||||||
|
|
|
@ -27,228 +27,220 @@ require('jquery.dotdotdot');
|
||||||
var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal');
|
var SchemaModal = require('../../Settings/NetImport/Add/NetImportSchemaModal');
|
||||||
|
|
||||||
module.exports = Marionette.Layout.extend({
|
module.exports = Marionette.Layout.extend({
|
||||||
template : 'AddMovies/List/AddFromListViewTemplate',
|
template: 'AddMovies/List/AddFromListViewTemplate',
|
||||||
|
|
||||||
regions : {
|
regions: {
|
||||||
fetchResult : '#fetch-result'
|
fetchResult: '#fetch-result'
|
||||||
},
|
},
|
||||||
|
|
||||||
ui : {
|
ui: {
|
||||||
moviesSearch : '.x-movies-search',
|
moviesSearch: '.x-movies-search',
|
||||||
listSelection : ".x-list-selection",
|
listSelection: ".x-list-selection",
|
||||||
importSelected : ".x-import-selected"
|
importSelected: ".x-import-selected"
|
||||||
},
|
},
|
||||||
|
|
||||||
columns : [
|
columns: [{
|
||||||
{
|
name: '',
|
||||||
name : '',
|
cell: SelectAllCell,
|
||||||
cell : SelectAllCell,
|
headerCell: 'select-all',
|
||||||
headerCell : 'select-all',
|
sortable: false
|
||||||
sortable : false
|
}, {
|
||||||
},
|
name: 'title',
|
||||||
{
|
label: 'Title',
|
||||||
name : 'title',
|
cell: MovieTitleCell,
|
||||||
label : 'Title',
|
cellValue: 'this',
|
||||||
cell : MovieTitleCell,
|
}, {
|
||||||
cellValue : 'this',
|
name: 'profileId',
|
||||||
},
|
label: 'Profile',
|
||||||
{
|
cell: ProfileCell,
|
||||||
name : 'profileId',
|
sortable: false,
|
||||||
label : 'Profile',
|
}, {
|
||||||
cell : ProfileCell
|
name: 'this',
|
||||||
},
|
label: 'Links',
|
||||||
{
|
cell: MovieLinksCell,
|
||||||
name : 'this',
|
className: "movie-links-cell",
|
||||||
label : 'Links',
|
sortable: false,
|
||||||
cell : MovieLinksCell,
|
}],
|
||||||
className : "movie-links-cell",
|
|
||||||
sortable : false,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
events : {
|
events: {
|
||||||
'click .x-load-more' : '_onLoadMore',
|
'click .x-load-more': '_onLoadMore',
|
||||||
"change .x-list-selection" : "_listSelected",
|
"change .x-list-selection": "_listSelected",
|
||||||
"click .x-fetch-list" : "_fetchList",
|
"click .x-fetch-list": "_fetchList",
|
||||||
"click .x-import-selected" : "_importSelected"
|
"click .x-import-selected": "_importSelected"
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize : function(options) {
|
initialize: function(options) {
|
||||||
console.log(options);
|
console.log(options);
|
||||||
|
|
||||||
this.isExisting = options.isExisting;
|
this.isExisting = options.isExisting;
|
||||||
//this.collection = new AddFromListCollection();
|
//this.collection = new AddFromListCollection();
|
||||||
|
|
||||||
this.templateHelpers = {}
|
this.templateHelpers = {}
|
||||||
this.listCollection = new ListCollection();
|
this.listCollection = new ListCollection();
|
||||||
this.templateHelpers.lists = this.listCollection.toJSON();
|
this.templateHelpers.lists = this.listCollection.toJSON();
|
||||||
|
|
||||||
this.listenTo(this.listCollection, 'all', this._listsUpdated);
|
this.listenTo(this.listCollection, 'all', this._listsUpdated);
|
||||||
this.listCollection.fetch();
|
this.listCollection.fetch();
|
||||||
|
|
||||||
this.collection = new AddFromListCollection();
|
this.collection = new AddFromListCollection();
|
||||||
|
|
||||||
this.listenTo(this.collection, 'sync', this._showResults);
|
this.listenTo(this.collection, 'sync', this._showResults);
|
||||||
|
|
||||||
/*this.listenTo(this.collection, 'sync', this._showResults);
|
/*this.listenTo(this.collection, 'sync', this._showResults);
|
||||||
|
|
||||||
this.resultCollectionView = new SearchResultCollectionView({
|
this.resultCollectionView = new SearchResultCollectionView({
|
||||||
collection : this.collection,
|
collection : this.collection,
|
||||||
isExisting : this.isExisting
|
isExisting : this.isExisting
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
//this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
//this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender : function() {
|
onRender: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.ui.importSelected.hide();
|
this.ui.importSelected.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow : function() {
|
onShow: function() {
|
||||||
this.ui.moviesSearch.focus();
|
this.ui.moviesSearch.focus();
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
search : function(options) {
|
},
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.collection.reset();
|
search: function(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
if (!options.term || options.term === this.collection.term) {
|
this.collection.reset();
|
||||||
return Marionette.$.Deferred().resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.searchResult.show(new LoadingView());
|
if (!options.term || options.term === this.collection.term) {
|
||||||
this.collection.term = options.term;
|
return Marionette.$.Deferred().resolve();
|
||||||
this.currentSearchPromise = this.collection.fetch({
|
}
|
||||||
data : { term : options.term }
|
|
||||||
});
|
|
||||||
|
|
||||||
this.currentSearchPromise.fail(function() {
|
this.searchResult.show(new LoadingView());
|
||||||
self._showError();
|
this.collection.term = options.term;
|
||||||
});
|
this.currentSearchPromise = this.collection.fetch({
|
||||||
|
data: { term: options.term }
|
||||||
|
});
|
||||||
|
|
||||||
return this.currentSearchPromise;
|
this.currentSearchPromise.fail(function() {
|
||||||
},
|
self._showError();
|
||||||
|
});
|
||||||
|
|
||||||
_onMoviesAdded : function(options) {
|
return this.currentSearchPromise;
|
||||||
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
|
},
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (!this.isExisting) {
|
_onMoviesAdded: function(options) {
|
||||||
this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
|
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
|
||||||
/*this.collection.term = '';
|
this.close();
|
||||||
this.collection.reset();
|
} else if (!this.isExisting) {
|
||||||
this._clearResults();
|
this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
|
||||||
this.ui.moviesSearch.val('');
|
/*this.collection.term = '';
|
||||||
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
|
this.collection.reset();
|
||||||
}
|
this._clearResults();
|
||||||
},
|
this.ui.moviesSearch.val('');
|
||||||
|
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_onLoadMore : function() {
|
_onLoadMore: function() {
|
||||||
var showingAll = this.resultCollectionView.showMore();
|
var showingAll = this.resultCollectionView.showMore();
|
||||||
this.ui.searchBar.show();
|
this.ui.searchBar.show();
|
||||||
|
|
||||||
if (showingAll) {
|
if (showingAll) {
|
||||||
this.ui.loadMore.hide();
|
this.ui.loadMore.hide();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_listSelected : function() {
|
_listSelected: function() {
|
||||||
var rootFolderValue = this.ui.listSelection.val();
|
var rootFolderValue = this.ui.listSelection.val();
|
||||||
if (rootFolderValue === 'addNew') {
|
if (rootFolderValue === 'addNew') {
|
||||||
//var rootFolderLayout = new SchemaModal(this.listCollection);
|
//var rootFolderLayout = new SchemaModal(this.listCollection);
|
||||||
//AppLayout.modalRegion.show(rootFolderLayout);
|
//AppLayout.modalRegion.show(rootFolderLayout);
|
||||||
SchemaModal.open(this.listCollection)
|
SchemaModal.open(this.listCollection)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetchList : function() {
|
_fetchList: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var listId = this.ui.listSelection.val();
|
var listId = this.ui.listSelection.val();
|
||||||
|
|
||||||
this.fetchResult.show(new LoadingView());
|
this.fetchResult.show(new LoadingView());
|
||||||
|
|
||||||
this.currentFetchPromise = this.collection.fetch(
|
this.currentFetchPromise = this.collection.fetch({ data: { listId: listId } })
|
||||||
{ data : { listId : listId} }
|
this.currentFetchPromise.fail(function() {
|
||||||
)
|
self._showError();
|
||||||
this.currentFetchPromise.fail(function() {
|
});
|
||||||
self._showError();
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_listsUpdated : function() {
|
_listsUpdated: function() {
|
||||||
this.templateHelpers.lists = this.listCollection.toJSON();
|
this.templateHelpers.lists = this.listCollection.toJSON();
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
_importSelected : function() {
|
_importSelected: function() {
|
||||||
var selected = this.importGrid.getSelectedModels();
|
var selected = this.importGrid.getSelectedModels();
|
||||||
// console.log(selected);
|
// console.log(selected);
|
||||||
var promise = MoviesCollection.importFromList(selected);
|
var promise = MoviesCollection.importFromList(selected);
|
||||||
this.ui.importSelected.spinForPromise(promise);
|
this.ui.importSelected.spinForPromise(promise);
|
||||||
this.ui.importSelected.addClass('disabled');
|
this.ui.importSelected.addClass('disabled');
|
||||||
|
|
||||||
Messenger.show({
|
Messenger.show({
|
||||||
message : "Importing {0} movies. Don't close this browser window until it has finished".format(selected.length),
|
message: "Importing {0} movies. Don't close this browser window until it has finished".format(selected.length),
|
||||||
hideOnNavigate : false,
|
hideOnNavigate: false,
|
||||||
hideAfter : 30,
|
hideAfter: 30,
|
||||||
type : "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
|
|
||||||
promise.done(function() {
|
promise.done(function() {
|
||||||
Messenger.show({
|
Messenger.show({
|
||||||
message : "Imported movies from list.",
|
message: "Imported movies from list.",
|
||||||
hideAfter : 8,
|
hideAfter: 8,
|
||||||
hideOnNavigate : true
|
hideOnNavigate: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/*for (m in selected) {
|
/*for (m in selected) {
|
||||||
debugger;
|
debugger;
|
||||||
m.save()
|
m.save()
|
||||||
MoviesCollection.add(m);
|
MoviesCollection.add(m);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//MoviesCollection.save();
|
//MoviesCollection.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
_clearResults : function() {
|
_clearResults: function() {
|
||||||
|
|
||||||
if (!this.isExisting) {
|
if (!this.isExisting) {
|
||||||
this.searchResult.show(new EmptyView());
|
this.searchResult.show(new EmptyView());
|
||||||
} else {
|
} else {
|
||||||
this.searchResult.close();
|
this.searchResult.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_showResults : function() {
|
_showResults: function() {
|
||||||
if (this.collection.length === 0) {
|
if (this.collection.length === 0) {
|
||||||
this.fetchResult.show(new NotFoundView({ term : "" }));
|
this.fetchResult.show(new NotFoundView({ term: "" }));
|
||||||
} else {
|
} else {
|
||||||
this.importGrid = new Backgrid.Grid({
|
this.importGrid = new Backgrid.Grid({
|
||||||
collection : this.collection,
|
collection: this.collection,
|
||||||
columns : this.columns,
|
columns: this.columns,
|
||||||
className : 'table table-hover'
|
className: 'table table-hover'
|
||||||
});
|
});
|
||||||
this.fetchResult.show(this.importGrid);
|
this.fetchResult.show(this.importGrid);
|
||||||
this.ui.importSelected.show();
|
this.ui.importSelected.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_abortExistingSearch : function() {
|
_abortExistingSearch: function() {
|
||||||
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
|
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
|
||||||
console.log('aborting previous pending search request.');
|
console.log('aborting previous pending search request.');
|
||||||
this.currentSearchPromise.abort();
|
this.currentSearchPromise.abort();
|
||||||
} else {
|
} else {
|
||||||
this._clearResults();
|
this._clearResults();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_showError : function() {
|
_showError: function() {
|
||||||
this.fetchResult.show(new ErrorView({ term : "" }));
|
this.fetchResult.show(new ErrorView({ term: "" }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<span class="series-info-links">
|
<span class="series-info-links">
|
||||||
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
|
{{#if tmdbId}}
|
||||||
{{#if website}}
|
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
|
||||||
<a href="{{homepage}}" class="label label-info">Homepage</a>
|
{{/if}}
|
||||||
|
{{#if website}}
|
||||||
|
<a href="{{homepage}}" class="label label-info">Homepage</a>
|
||||||
|
{{/if}}
|
||||||
|
{{#if tmdbId}}
|
||||||
|
<a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<a href="{{tmdbUrl}}" class="label label-info">The Movie DB</a>
|
|
||||||
|
|
||||||
{{#if imdbId}}
|
{{#if imdbId}}
|
||||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
<a href="{{imdbUrl}}">{{title}}</a>
|
{{#if imdbId}}
|
||||||
|
<a href="{{imdbUrl}}">{{title}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a href="{{tmdbUrl}}">{{title}}</a>
|
||||||
|
{{/if}}
|
Loading…
Add table
Add a link
Reference in a new issue