Support for Gazelle Indexers (RED, AR)

This commit is contained in:
Qstick
2021-02-14 00:47:21 -05:00
parent 0fb7168669
commit 30367b53fd
71 changed files with 1196 additions and 1933 deletions
@@ -0,0 +1,70 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNet : HttpIndexerBase<BroadcastheNetSettings>
{
public override string Name => "BroadcasTheNet";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override string BaseUrl => "http://api.broadcasthe.net/";
public BroadcastheNet(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
var requestGenerator = new BroadcastheNetRequestGenerator() { Settings = Settings, PageSize = PageSize, BaseUrl = BaseUrl };
var releaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
if (releaseInfo != null)
{
int torrentID;
if (int.TryParse(releaseInfo.Guid.Replace("BTN-", string.Empty), out torrentID))
{
requestGenerator.LastRecentTorrentID = torrentID;
}
}
return requestGenerator;
}
public override IParseIndexerResponse GetParser()
{
return new BroadcastheNetParser();
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
LimitsDefault = 100,
LimitsMax = 1000,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}
};
caps.Categories.AddCategoryMapping("SD", NewznabStandardCategory.TVSD, "SD");
caps.Categories.AddCategoryMapping("720p", NewznabStandardCategory.TVHD, "720p");
caps.Categories.AddCategoryMapping("1080p", NewznabStandardCategory.TVHD, "1080p");
caps.Categories.AddCategoryMapping("1080i", NewznabStandardCategory.TVHD, "1080i");
caps.Categories.AddCategoryMapping("2160p", NewznabStandardCategory.TVHD, "2160p");
caps.Categories.AddCategoryMapping("Portable Device", NewznabStandardCategory.TVSD, "Portable Device");
return caps;
}
}
}
@@ -0,0 +1,109 @@
using System;
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.BroadcastheNet
{
public class BroadcastheNetParser : IParseIndexerResponse
{
private static readonly Regex RegexProtocol = new Regex("^https?:", RegexOptions.Compiled);
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var results = new List<ReleaseInfo>();
switch (indexerResponse.HttpResponse.StatusCode)
{
case HttpStatusCode.Unauthorized:
throw new ApiKeyException("API Key invalid or not authorized");
case HttpStatusCode.NotFound:
throw new IndexerException(indexerResponse, "Indexer API call returned NotFound, the Indexer API may have changed.");
case HttpStatusCode.ServiceUnavailable:
throw new RequestLimitReachedException("Cannot do more than 150 API requests per hour.");
default:
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Indexer API call returned an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
break;
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html"))
{
throw new IndexerException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
if (indexerResponse.Content == "Query execution was interrupted")
{
throw new IndexerException(indexerResponse, "Indexer API returned an internal server error");
}
JsonRpcResponse<BroadcastheNetTorrents> jsonResponse = new HttpResponse<JsonRpcResponse<BroadcastheNetTorrents>>(indexerResponse.HttpResponse).Resource;
if (jsonResponse.Error != null || jsonResponse.Result == null)
{
throw new IndexerException(indexerResponse, "Indexer API call returned an error [{0}]", jsonResponse.Error);
}
if (jsonResponse.Result.Results == 0)
{
return results;
}
var protocol = indexerResponse.HttpRequest.Url.Scheme + ":";
foreach (var torrent in jsonResponse.Result.Torrents.Values)
{
var torrentInfo = new TorrentInfo();
torrentInfo.Guid = string.Format("BTN-{0}", torrent.TorrentID);
torrentInfo.Title = CleanReleaseName(torrent.ReleaseName);
torrentInfo.Size = torrent.Size;
torrentInfo.DownloadUrl = RegexProtocol.Replace(torrent.DownloadURL, protocol);
torrentInfo.InfoUrl = string.Format("{0}//broadcasthe.net/torrents.php?id={1}&torrentid={2}", protocol, torrent.GroupID, torrent.TorrentID);
//torrentInfo.CommentUrl =
if (torrent.TvdbID.HasValue)
{
torrentInfo.TvdbId = torrent.TvdbID.Value;
}
if (torrent.TvrageID.HasValue)
{
torrentInfo.TvRageId = torrent.TvrageID.Value;
}
torrentInfo.PublishDate = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).ToUniversalTime().AddSeconds(torrent.Time);
//torrentInfo.MagnetUrl =
torrentInfo.InfoHash = torrent.InfoHash;
torrentInfo.Seeders = torrent.Seeders;
torrentInfo.Peers = torrent.Leechers + torrent.Seeders;
torrentInfo.Origin = torrent.Origin;
torrentInfo.Source = torrent.Source;
torrentInfo.Container = torrent.Container;
torrentInfo.Codec = torrent.Codec;
torrentInfo.Resolution = torrent.Resolution;
results.Add(torrentInfo);
}
return results;
}
private string CleanReleaseName(string releaseName)
{
releaseName = releaseName.Replace("\\", "");
return releaseName;
}
}
}
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetRequestGenerator : IIndexerRequestGenerator
{
public int MaxPages { get; set; }
public int PageSize { get; set; }
public BroadcastheNetSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public int? LastRecentTorrentID { get; set; }
public System.Func<IDictionary<string, string>> GetCookies { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public System.Action<IDictionary<string, string>, System.DateTime?> CookiesUpdater { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public string BaseUrl { get; set; }
public BroadcastheNetRequestGenerator()
{
MaxPages = 10;
PageSize = 100;
}
private IEnumerable<IndexerRequest> GetPagedRequests(BroadcastheNetTorrentQuery parameters, int results, int offset)
{
var builder = new JsonRpcRequestBuilder(BaseUrl)
.Call("getTorrents", Settings.ApiKey, parameters, results, offset);
builder.SuppressHttpError = true;
yield return new IndexerRequest(builder.Build());
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
{
btnResults = (int)Capabilities.LimitsDefault;
}
var btnOffset = searchCriteria.Offset.GetValueOrDefault();
if (searchCriteria.TvdbId != 0)
{
parameters.Tvdb = string.Format("{0}", searchCriteria.TvdbId);
}
if (searchCriteria.RId != 0)
{
parameters.Tvrage = string.Format("{0}", searchCriteria.RId);
}
// If only the season/episode is searched for then change format to match expected format
if (searchCriteria.Season > 0 && searchCriteria.Ep == null)
{
parameters.Name = string.Format("Season {0}%", searchCriteria.Season.Value);
parameters.Category = "Season";
}
else if (searchCriteria.Season > 0 && searchCriteria.Ep.Value > 0)
{
parameters.Name = string.Format("S{0:00}E{1:00}", searchCriteria.Season.Value, searchCriteria.Ep.Value);
parameters.Category = "Episode";
}
parameters.Search = searchString.Replace(" ", "%");
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0)
{
btnResults = (int)Capabilities.LimitsDefault;
}
parameters.Search = searchString.Replace(" ", "%");
var btnOffset = searchCriteria.Offset.GetValueOrDefault();
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
return pageableRequests;
}
}
}
@@ -0,0 +1,32 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetSettingsValidator : AbstractValidator<BroadcastheNetSettings>
{
public BroadcastheNetSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class BroadcastheNetSettings : IProviderConfig
{
private static readonly BroadcastheNetSettingsValidator Validator = new BroadcastheNetSettingsValidator();
public BroadcastheNetSettings()
{
}
[FieldDefinition(1, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}
@@ -0,0 +1,31 @@
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrent
{
public string GroupName { get; set; }
public int GroupID { get; set; }
public int TorrentID { get; set; }
public int SeriesID { get; set; }
public string Series { get; set; }
public string SeriesBanner { get; set; }
public string SeriesPoster { get; set; }
public string YoutubeTrailer { get; set; }
public string Category { get; set; }
public int? Snatched { get; set; }
public int? Seeders { get; set; }
public int? Leechers { get; set; }
public string Source { get; set; }
public string Container { get; set; }
public string Codec { get; set; }
public string Resolution { get; set; }
public string Origin { get; set; }
public string ReleaseName { get; set; }
public long Size { get; set; }
public long Time { get; set; }
public int? TvdbID { get; set; }
public int? TvrageID { get; set; }
public string ImdbID { get; set; }
public string InfoHash { get; set; }
public string DownloadURL { get; set; }
}
}
@@ -0,0 +1,39 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrentQuery
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Category { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Name { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Search { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Codec { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Container { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Source { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Resolution { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Origin { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Hash { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Tvdb { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Tvrage { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Age { get; set; }
public BroadcastheNetTorrentQuery Clone()
{
return MemberwiseClone() as BroadcastheNetTorrentQuery;
}
}
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.BroadcastheNet
{
public class BroadcastheNetTorrents
{
public Dictionary<int, BroadcastheNetTorrent> Torrents { get; set; }
public int Results { get; set; }
}
}