diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs new file mode 100644 index 000000000..2eeb09f37 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotato.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Http.CloudFlare; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotato : HttpIndexerBase + { + public override string Name => "TorrentPotato"; + + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override TimeSpan RateLimit => TimeSpan.FromSeconds(2); + + public TorrentPotato(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, indexerStatusService, configService, parsingService, logger) + { + + } + + public override IEnumerable DefaultDefinitions + { + get + { + yield return GetDefinition("Jackett", new TorrentPotatoSettings { BaseUrl = "http://localhost:9117/potato/YOURINDEXER"}); + } + } + + private IndexerDefinition GetDefinition(string name, TorrentPotatoSettings settings) + { + return new IndexerDefinition + { + EnableRss = false, + EnableSearch = false, + Name = name, + Implementation = GetType().Name, + Settings = settings, + Protocol = DownloadProtocol.Torrent, + SupportsRss = SupportsRss, + SupportsSearch = SupportsSearch + }; + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new TorrentPotatoRequestGenerator() { Settings = Settings }; + } + + public override IParseIndexerResponse GetParser() + { + return new TorrentPotatoParser(); + } + + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs new file mode 100644 index 000000000..2c21e408e --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoParser.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoParser : IParseIndexerResponse + { + private static readonly Regex RegexGuid = new Regex(@"^magnet:\?xt=urn:btih:([a-f0-9]+)", RegexOptions.Compiled); + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var results = new List(); + + switch (indexerResponse.HttpResponse.StatusCode) + { + default: + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + { + throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + break; + } + + var jsonResponse = new HttpResponse(indexerResponse.HttpResponse); + + foreach (var torrent in jsonResponse.Resource.results) + { + var torrentInfo = new TorrentInfo(); + + torrentInfo.Guid = GetGuid(torrent); + torrentInfo.Title = torrent.release_name; + torrentInfo.Size = (long)torrent.size*1000*1000; + torrentInfo.DownloadUrl = torrent.download_url; + torrentInfo.InfoUrl = torrent.details_url; + torrentInfo.PublishDate = new System.DateTime(); + torrentInfo.Seeders = torrent.seeders; + torrentInfo.Peers = torrent.leechers + torrent.seeders; + torrentInfo.Freeleech = torrent.freeleech; + + results.Add(torrentInfo); + } + + return results; + } + + private string GetGuid(Result torrent) + { + var match = RegexGuid.Match(torrent.download_url); + + if (match.Success) + { + return string.Format("potato-{0}", match.Groups[1].Value); + } + else + { + return string.Format("potato-{0}", torrent.download_url); + } + } + + } +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs new file mode 100644 index 000000000..5509c0754 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoRequestGenerator.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator + { + + public TorrentPotatoSettings Settings { get; set; } + + public TorrentPotatoRequestGenerator() + { + + } + + public virtual IndexerPageableRequestChain GetRecentRequests() + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("list", null, null)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "S{0:00}E{1:00}", searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "S{0:00}", searchCriteria.SeasonNumber)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, "\"{0:yyyy MM dd}\"", searchCriteria.AirDate)); + + return pageableRequests; + } + + public virtual IndexerPageableRequestChain GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public virtual IndexerPageableRequestChain GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + foreach (var queryTitle in searchCriteria.EpisodeQueryTitles) + { + var query = queryTitle.Replace('+', ' '); + query = System.Web.HttpUtility.UrlEncode(query); + + pageableRequests.Add(GetPagedRequests("search", searchCriteria.Series.TvdbId, query)); + } + + return pageableRequests; + } + + private IEnumerable GetPagedRequests(string mode, int? tvdbId, string query, params object[] args) + { + var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl) + .Accept(HttpAccept.Json); + + requestBuilder.AddQueryParam("passkey", Settings.Passkey); + requestBuilder.AddQueryParam("user", Settings.User); + requestBuilder.AddQueryParam("imdbid", "tt0076759"); //For now just search for Star Wars. + + + + yield return new IndexerRequest(requestBuilder.Build()); + } + + private IEnumerable GetMovieRequest(MovieSearchCriteria searchCriteria) + { + var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl) + .Accept(HttpAccept.Json); + + requestBuilder.AddQueryParam("passkey", Settings.Passkey); + requestBuilder.AddQueryParam("user", Settings.User); + + if (searchCriteria.Movie.ImdbId.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddQueryParam("imdbid", searchCriteria.Movie.ImdbId); + } + else + { + requestBuilder.AddQueryParam("search", searchCriteria.Movie.Title); + } + + yield return new IndexerRequest(requestBuilder.Build()); + } + + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + + + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetMovieRequest(searchCriteria)); + + return pageableRequests; + + + } + } +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs new file mode 100644 index 000000000..9b8300405 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoResponse.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + + public class TorrentPotatoResponse + { + public Result[] results { get; set; } + public int total_results { get; set; } + } + + public class Result + { + public string release_name { get; set; } + public string torrent_id { get; set; } + public string details_url { get; set; } + public string download_url { get; set; } + public bool freeleech { get; set; } + public string type { get; set; } + public int size { get; set; } + public int leechers { get; set; } + public int seeders { get; set; } + } + +} diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs new file mode 100644 index 000000000..d0b902f5e --- /dev/null +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.TorrentPotato +{ + public class TorrentPotatoSettingsValidator : AbstractValidator + { + public TorrentPotatoSettingsValidator() + { + RuleFor(c => c.BaseUrl).ValidRootUrl(); + } + } + + public class TorrentPotatoSettings : IProviderConfig + { + private static readonly TorrentPotatoSettingsValidator Validator = new TorrentPotatoSettingsValidator(); + + public TorrentPotatoSettings() + { + BaseUrl = ""; + } + + [FieldDefinition(0, Label = "API URL", HelpText = "URL to TorrentPotato api.")] + public string BaseUrl { get; set; } + + [FieldDefinition(1, Label = "Username", HelpText = "The username you use at your indexer.")] + public string User { get; set; } + + [FieldDefinition(2, Label = "Passkey", HelpText = "The password you use at your Indexer,")] + public string Passkey { get; set; } + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 06c19aaa5..1de7d14ea 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -633,6 +633,11 @@ + + + + + diff --git a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs index 59aab44a0..dbbf7f1e9 100644 --- a/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/TorrentInfo.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Parser.Model public string InfoHash { get; set; } public int? Seeders { get; set; } public int? Peers { get; set; } + public bool Freeleech { get; set; } public static int? GetSeeders(ReleaseInfo release) {