diff --git a/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesException.cs b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesException.cs new file mode 100644 index 000000000..022b51ef8 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesException.cs @@ -0,0 +1,31 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.ImportLists.NYTimes +{ + public class NYTimesException : NzbDroneException + { + public NYTimesException(string message) + : base(message) + { + } + + public NYTimesException(string message, params object[] args) + : base(message, args) + { + } + + public NYTimesException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + public class NYTimesAuthorizationException : NYTimesException + { + public NYTimesAuthorizationException(string message) + : base(message) + { + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImport.cs b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImport.cs new file mode 100644 index 000000000..c563efbe9 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImport.cs @@ -0,0 +1,147 @@ +using FluentValidation.Results; +using Newtonsoft.Json; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.ImportLists.NYTimes +{ + public class NYTimesImport : ImportListBase + { + protected readonly IHttpClient _httpClient; + + public override string Name => "New York Times"; + + public override ImportListType ListType => ImportListType.Other; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); + + public NYTimesImport(IImportListStatusService importListStatusService, + IConfigService configService, + IParsingService parsingService, + IHttpClient httpClient, + Logger logger) + : base(importListStatusService, configService, parsingService, logger) + { + _httpClient = httpClient; + } + + public List GetNames(NYTimesSettings settings) + { + if (settings.ApiKey.IsNullOrWhiteSpace()) + { + return new List(); + } + + var request = new HttpRequestBuilder(settings.BaseUrl) + .Resource("/lists/names.json") + .AddQueryParam("api-key", settings.ApiKey) + .Build(); + + var response = _httpClient.Get(request); + + var content = JsonConvert.DeserializeObject>>(response.Content); + + return content.Results; + } + + public List GetList(NYTimesSettings settings) + { + if (settings.ApiKey.IsNullOrWhiteSpace() || settings.ListName.IsNullOrWhiteSpace()) + { + return new List(); + } + + var request = new HttpRequestBuilder(settings.BaseUrl) + .Resource("/lists.json") + .AddQueryParam("api-key", settings.ApiKey) + .AddQueryParam("list", settings.ListName) + .Build(); + + var response = _httpClient.Get(request); + + var content = JsonConvert.DeserializeObject>>(response.Content); + + return content.Results; + } + + public override IList Fetch() + { + var books = new List(); + + try + { + var lists = GetList(Settings); + + foreach (var list in lists) + { + var book = list.BookDetails.FirstOrDefault(); + + books.Add(new ImportListItemInfo + { + Author = book.Author, + Book = book.Title + }); + } + + _importListStatusService.RecordSuccess(Definition.Id); + } + catch + { + _logger.Warn("List Import Sync Task Failed for List [{0}]", Definition.Name); + _importListStatusService.RecordFailure(Definition.Id); + } + + return CleanupListItems(books); + } + + protected override void Test(List failures) + { + failures.AddIfNotNull(TestConnection()); + } + + private ValidationFailure TestConnection() + { + try + { + GetNames(Settings); + return null; + } + catch (HttpException ex) + { + _logger.Warn(ex, "New York Times Authentication Error"); + return new ValidationFailure(string.Empty, $"NYTimes authentication error: {ex.Message}"); + } + catch (Exception ex) + { + _logger.Warn(ex, "Unable to connect to NYTimes"); + + return new ValidationFailure(string.Empty, "Unable to connect to import list, check the log for more details"); + } + } + + public override object RequestAction(string action, IDictionary query) + { + if (action == "getNames") + { + var names = GetNames(Settings); + + return new + { + options = names.Select(name => new + { + Value = name.ListNameEncoded, + Name = name.DisplayName + }) + }; + } + + return new { }; + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImportApi.cs b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImportApi.cs new file mode 100644 index 000000000..f619184b1 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesImportApi.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.ImportLists.NYTimes +{ + public class NYTimesResponse + { + public string Status { get; set; } + public string Copyright { get; set; } + [JsonProperty(PropertyName = "num_results")] + public int NumResults { get; set; } + [JsonProperty(PropertyName = "last_modified")] + public string LastModified { get; set; } + public T Results { get; set; } + } + + public class NYTimesName + { + [JsonProperty(PropertyName = "list_name")] + public string ListName { get; set; } + [JsonProperty(PropertyName = "display_name")] + public string DisplayName { get; set; } + [JsonProperty(PropertyName = "list_name_encoded")] + public string ListNameEncoded { get; set; } + [JsonProperty(PropertyName = "oldest_published_date")] + public string OldestPublishedDate { get; set; } + [JsonProperty(PropertyName = "newest_published_date")] + public string NewestPublishedDate { get; set; } + public string Updated { get; set; } + } + + public class NYTimesList + { + [JsonProperty(PropertyName = "list_name")] + public string ListName { get; set; } + [JsonProperty(PropertyName = "display_name")] + public string DisplayName { get; set; } + [JsonProperty(PropertyName = "bestsellers_date")] + public string BestsellersDate { get; set; } + [JsonProperty(PropertyName = "published_date")] + public string PublishedDate { get; set; } + public int Rank { get; set; } + [JsonProperty(PropertyName = "rank_last_week")] + public int RankLastWeek { get; set; } + [JsonProperty(PropertyName = "weeks_on_list")] + public int WeeksonList { get; set; } + public int Asterisk { get; set; } + public int Dagger { get; set; } + [JsonProperty(PropertyName = "amazon_product_url")] + public string AmazonProductUrl { get; set; } + public object[] Isbns { get; set; } + [JsonProperty(PropertyName = "book_details")] + public List BookDetails { get; set; } + public object[] Reviews { get; set; } + } + + public class NYTimesBook + { + [JsonProperty(PropertyName = "age_group")] + public string AgeGroup { get; set; } + public string Author { get; set; } + public string Contributor { get; set; } + [JsonProperty(PropertyName = "contributor_note")] + public string ContributorNote { get; set; } + [JsonProperty(PropertyName = "created_date")] + public string CreatedDate { get; set; } + public string Description { get; set; } + public string Price { get; set; } + [JsonProperty(PropertyName = "primary_isbn13")] + public string PrimaryIsbn13 { get; set; } + [JsonProperty(PropertyName = "primary_isbn10")] + public string PrimaryIsbn10 { get; set; } + public string Publisher { get; set; } + public int Rank { get; set; } + public string Title { get; set; } + [JsonProperty(PropertyName = "updated_date")] + public string UpdatedDate { get; set; } + } +} diff --git a/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesSettings.cs b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesSettings.cs new file mode 100644 index 000000000..084357055 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/NYTimes/NYTimesSettings.cs @@ -0,0 +1,37 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.ImportLists.NYTimes +{ + public class NYTimesSettingsValidator : AbstractValidator + { + public NYTimesSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + RuleFor(c => c.ListName).NotEmpty(); + } + } + + public class NYTimesSettings : IImportListSettings + { + private static readonly NYTimesSettingsValidator Validator = new (); + public NYTimesSettings() + { + BaseUrl = "https://api.nytimes.com/svc/books/v3"; + } + + public string BaseUrl { get; set; } + + [FieldDefinition(0, Label = "API Key")] + public string ApiKey { get; set; } + + [FieldDefinition(1, Type = FieldType.Select, SelectOptionsProviderAction = "getNames", Label = "List Type", HelpText = "The name of the Times best sellers list")] + public string ListName { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +}