From d1372f4d00a43493bb76759cdda20c542bc371e4 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sun, 8 Jan 2017 14:26:36 -0500 Subject: [PATCH] Added PassThePopcorn indexe --- .../Indexers/PassThePopcorn/PassThePopcorn.cs | 30 +++++++ .../PassThePopcorn/PassThePopcornApi.cs | 58 +++++++++++++ .../PassThePopcorn/PassThePopcornParser.cs | 86 +++++++++++++++++++ .../PassThePopcornRequestGenerator.cs | 67 +++++++++++++++ .../PassThePopcorn/PassThePopcornSettings.cs | 45 ++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 5 ++ 6 files changed, 291 insertions(+) create mode 100644 src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcorn.cs create mode 100644 src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornApi.cs create mode 100644 src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornParser.cs create mode 100644 src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornRequestGenerator.cs create mode 100644 src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcorn.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcorn.cs new file mode 100644 index 0000000000..301894f57e --- /dev/null +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcorn.cs @@ -0,0 +1,30 @@ +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Indexers.PassThePopcorn +{ + public class PassThePopcorn : HttpIndexerBase + { + public override string Name => "PassThePopcorn"; + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override bool SupportsRss => true; + public override bool SupportsSearch => true; + public override int PageSize => 50; + + public PassThePopcorn(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, indexerStatusService, configService, parsingService, logger) + { } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new PassThePopcornRequestGenerator() { Settings = Settings }; + } + + public override IParseIndexerResponse GetParser() + { + return new PassThePopcornParser(Settings); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornApi.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornApi.cs new file mode 100644 index 0000000000..efb8230e4d --- /dev/null +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornApi.cs @@ -0,0 +1,58 @@ +using System; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace NzbDrone.Core.Indexers.PassThePopcorn +{ + public class Director + { + public string Name { get; set; } + public string Id { get; set; } + } + + public class Torrent + { + public int Id { get; set; } + public string Quality { get; set; } + public string Source { get; set; } + public string Container { get; set; } + public string Codec { get; set; } + public string Resolution { get; set; } + public bool Scene { get; set; } + public string Size { get; set; } + public DateTime UploadTime { get; set; } + public string Snatched { get; set; } + public string Seeders { get; set; } + public string Leechers { get; set; } + public string ReleaseName { get; set; } + public bool Checked { get; set; } + public bool GoldenPopcorn { get; set; } + } + + public class Movie + { + public string GroupId { get; set; } + public string Title { get; set; } + public string Year { get; set; } + public string Cover { get; set; } + public List Tags { get; set; } + public List Directors { get; set; } + public string ImdbId { get; set; } + public int TotalLeechers { get; set; } + public int TotalSeeders { get; set; } + public int TotalSnatched { get; set; } + public long MaxSize { get; set; } + public string LastUploadTime { get; set; } + public List Torrents { get; set; } + } + + public class PassThePopcornResponse + { + public string TotalResults { get; set; } + public List Movies { get; set; } + public string Page { get; set; } + public string AuthKey { get; set; } + public string PassKey { get; set; } + } + +} diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornParser.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornParser.cs new file mode 100644 index 0000000000..723e33bf20 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornParser.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Net; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; +using System; + +namespace NzbDrone.Core.Indexers.PassThePopcorn +{ + public class PassThePopcornParser : IParseIndexerResponse + { + private readonly PassThePopcornSettings _settings; + + public PassThePopcornParser(PassThePopcornSettings settings) + { + _settings = settings; + } + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var torrentInfos = new List(); + + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new IndexerException(indexerResponse, + "Unexpected response status {0} code from API request", + indexerResponse.HttpResponse.StatusCode); + } + + var jsonResponse = JsonConvert.DeserializeObject(indexerResponse.Content); + + var responseData = jsonResponse.Movies; + if (responseData == null) + { + throw new IndexerException(indexerResponse, + "Indexer API call response missing result data"); + } + + foreach (var result in responseData) + { + foreach (var torrent in result.Torrents) + { + var id = torrent.Id; + torrentInfos.Add(new TorrentInfo() + { + Guid = string.Format("PassThePopcorn-{0}", id), + Title = torrent.ReleaseName, + Size = Int64.Parse(torrent.Size), + DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), + InfoUrl = GetInfoUrl(result.GroupId, id), + Seeders = Int32.Parse(torrent.Seeders), + Peers = Int32.Parse(torrent.Leechers) + Int32.Parse(torrent.Seeders), + PublishDate = torrent.UploadTime.ToUniversalTime() + }); + } + } + + return torrentInfos.ToArray(); + } + + private string GetDownloadUrl(int torrentId, string authKey, string passKey) + { + var url = new HttpUri(_settings.BaseUrl) + .CombinePath("/torrents.php") + .AddQueryParam("action", "download") + .AddQueryParam("id", torrentId) + .AddQueryParam("authkey", authKey) + .AddQueryParam("torrent_pass", passKey); + + return url.FullUri; + } + + private string GetInfoUrl(string groupId, int torrentId) + { + var url = new HttpUri(_settings.BaseUrl) + .CombinePath("/torrents.php") + .AddQueryParam("id", groupId) + .AddQueryParam("torrentid", torrentId); + + return url.FullUri; + + } + } +} diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornRequestGenerator.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornRequestGenerator.cs new file mode 100644 index 0000000000..6b58aaac37 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornRequestGenerator.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.PassThePopcorn +{ + public class PassThePopcornRequestGenerator : IIndexerRequestGenerator + { + public PassThePopcornSettings Settings { get; set; } + + public virtual IndexerPageableRequestChain GetRecentRequests() + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetRequest("")); + + return pageableRequests; + } + + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + pageableRequests.Add(GetRequest(searchCriteria.Movie.ImdbId)); + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + private IEnumerable GetRequest(string query) + { + var request = new IndexerRequest(string.Format("{0}/torrents.php?json=noredirect&searchstr={1}", Settings.BaseUrl.Trim().TrimEnd('/'), query), HttpAccept.Json); + + foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie)) + { + request.HttpRequest.Cookies[cookie.Key] = cookie.Value; + } + + yield return request; + } + } +} diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs new file mode 100644 index 0000000000..fb99b848df --- /dev/null +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; +using System.Text.RegularExpressions; + +namespace NzbDrone.Core.Indexers.PassThePopcorn +{ + public class PassThePopcornSettingsValidator : AbstractValidator + { + public PassThePopcornSettingsValidator() + { + RuleFor(c => c.BaseUrl).ValidRootUrl(); + RuleFor(c => c.Cookie).NotEmpty(); + + RuleFor(c => c.Cookie) + .Matches(@"__cfduid=[0-9a-f]{43}", RegexOptions.IgnoreCase) + .WithMessage("Wrong pattern") + .AsWarning(); + } + } + + public class PassThePopcornSettings : IProviderConfig + { + private static readonly PassThePopcornSettingsValidator Validator = new PassThePopcornSettingsValidator(); + + public PassThePopcornSettings() + { + BaseUrl = "https://passthepopcorn.me"; + } + + [FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your cookie will be sent to that host.")] + public string BaseUrl { get; set; } + + [FieldDefinition(1, Label = "Cookie", HelpText = "PassThePopcorn uses a login cookie needed to access the API, you'll have to retrieve it via a browser.")] + public string Cookie { get; set; } + + + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 8720a43ff9..6ac2bee76d 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -592,6 +592,11 @@ + + + + +