From 1955392bebc9a307953b2243d171c7548ed7bf6d Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 12 Nov 2023 01:42:57 +0200 Subject: [PATCH] New: PassThePopcorn collection as import lists Co-authored-by: Winter <78392041+winterqt@users.noreply.github.com> --- .../PassThePopcornCollectionImport.cs | 31 +++++++++++ .../PassThePopcornCollectionParser.cs | 29 +++++++++++ ...assThePopcornCollectionRequestGenerator.cs | 51 ++++++++++++++++++ .../PassThePopcornCollectionResponse.cs | 20 +++++++ .../PassThePopcornCollectionSettings.cs | 52 +++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionImport.cs create mode 100644 src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionParser.cs create mode 100644 src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionRequestGenerator.cs create mode 100644 src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionResponse.cs create mode 100644 src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionSettings.cs diff --git a/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionImport.cs b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionImport.cs new file mode 100644 index 000000000..48181502d --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionImport.cs @@ -0,0 +1,31 @@ +using System; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.ImportLists.PassThePopcorn.Collection; + +public class PassThePopcornCollectionImport : HttpImportListBase +{ + public override string Name => "PassThePopcorn Collection"; + public override ImportListType ListType => ImportListType.Other; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); + public override int PageSize => 60; + public override TimeSpan RateLimit => TimeSpan.FromSeconds(10); + + public PassThePopcornCollectionImport(IHttpClient httpClient, IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, importListStatusService, configService, parsingService, logger) + { + } + + public override IImportListRequestGenerator GetRequestGenerator() + { + return new PassThePopcornCollectionRequestGenerator(Settings); + } + + public override IParseImportListResponse GetParser() + { + return new PassThePopcornCollectionParser(); + } +} diff --git a/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionParser.cs b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionParser.cs new file mode 100644 index 000000000..c95d95d19 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionParser.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.ImportLists.Exceptions; +using NzbDrone.Core.ImportLists.ImportListMovies; + +namespace NzbDrone.Core.ImportLists.PassThePopcorn.Collection; + +public class PassThePopcornCollectionParser : IParseImportListResponse +{ + public IList ParseResponse(ImportListResponse importListResponse) + { + if (!STJson.TryDeserialize(importListResponse.Content, out var jsonResponse)) + { + throw new ImportListException(importListResponse, "List responded with invalid JSON content. Site is likely blocked or unavailable."); + } + + return jsonResponse.CoverView + .Movies + .Select(item => new ImportListMovie + { + ImdbId = item.ImdbId, + Title = WebUtility.HtmlDecode(item.Title), + Year = int.Parse(item.Year) + }) + .ToList(); + } +} diff --git a/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionRequestGenerator.cs new file mode 100644 index 000000000..a02cb182b --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionRequestGenerator.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using NzbDrone.Common.Http; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.ImportLists.PassThePopcorn.Collection; + +public class PassThePopcornCollectionRequestGenerator : IImportListRequestGenerator +{ + private readonly PassThePopcornCollectionSettings _settings; + + public PassThePopcornCollectionRequestGenerator(PassThePopcornCollectionSettings settings) + { + _settings = settings; + } + + public ImportListPageableRequestChain GetMovies() + { + var pageableRequests = new ImportListPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests()); + + return pageableRequests; + } + + private IEnumerable GetPagedRequests() + { + _settings.Validate().Filter("ApiUser", "ApiKey", "MaxPages").ThrowOnError(); + + var requestBuilder = BuildRequest(); + + for (var pageNumber = 1; pageNumber <= _settings.MaxPages; pageNumber++) + { + requestBuilder.AddQueryParam("page", pageNumber, true); + + var request = requestBuilder.Build(); + + yield return new ImportListRequest(request); + } + } + + private HttpRequestBuilder BuildRequest() + { + return new HttpRequestBuilder(_settings.CollectionUrl) + .Accept(HttpAccept.Json) + .SetHeader("ApiUser", _settings.ApiUser) + .SetHeader("ApiKey", _settings.ApiKey) + .AddQueryParam("action", "get_page") + .AddQueryParam("filter_cat[1]", "1") + .WithRateLimit(5); + } +} diff --git a/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionResponse.cs b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionResponse.cs new file mode 100644 index 000000000..cbed25a7b --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.ImportLists.PassThePopcorn.Collection; + +internal class PassThePopcornCollectionResponse +{ + public PassThePopcornCoverView CoverView { get; set; } +} + +internal class PassThePopcornCoverView +{ + public IList Movies { get; set; } +} + +internal class PassThePopcornCollectionMovie +{ + public string Title { get; set; } + public string Year { get; set; } + public string ImdbId { get; set; } +} diff --git a/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionSettings.cs b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionSettings.cs new file mode 100644 index 000000000..a8999d5a0 --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/PassThePopcorn/Collection/PassThePopcornCollectionSettings.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.ImportLists.PassThePopcorn.Collection; + +public class PassThePopcornCollectionSettingsValidator : AbstractValidator +{ + public PassThePopcornCollectionSettingsValidator() + { + RuleFor(c => c.CollectionUrl).NotEmpty().IsValidUrl(); + + RuleFor(c => c.CollectionUrl) + .Matches(@"^https://passthepopcorn\.me/collages\.php\?id=\d+(?:&[\w-]+(=[\w-]*)?)*?$", RegexOptions.IgnoreCase) + .WithMessage("Invalid Collection URL. Acceptable format: 'https://passthepopcorn.me/collages.php?id=21&order_by=year&order_way=desc'"); + + RuleFor(c => c.ApiUser).NotEmpty(); + RuleFor(c => c.ApiKey).NotEmpty(); + + RuleFor(c => c.MaxPages).InclusiveBetween(1, 20); + } +} + +public class PassThePopcornCollectionSettings : IProviderConfig +{ + private static readonly PassThePopcornCollectionSettingsValidator Validator = new (); + + public PassThePopcornCollectionSettings() + { + CollectionUrl = "https://passthepopcorn.me/collages.php?id=21"; + MaxPages = 5; + } + + [FieldDefinition(0, Label = "Collection URL", HelpText = "Provide a fully URL to your wanted collection, including filters to your liking.", HelpTextWarning = "By default only the category Feature Film will be imported.", HelpLink = "https://passthepopcorn.me/collages.php")] + public string CollectionUrl { get; set; } + + [FieldDefinition(1, Label = "API User", HelpText = "These settings are found in your PassThePopcorn security settings (Edit Profile > Security).", Privacy = PrivacyLevel.UserName)] + public string ApiUser { get; set; } + + [FieldDefinition(2, Label = "API Key", Type = FieldType.Password, Privacy = PrivacyLevel.Password)] + public string ApiKey { get; set; } + + [FieldDefinition(3, Label = "Max Pages", HelpText = "Number of pages to pull from list (Max 20)", Type = FieldType.Number)] + public int MaxPages { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } +}