New: Indexer - PrivateHD

pull/25/head
Qstick 4 years ago
parent a3f630a6f6
commit bf0c095d55

@ -0,0 +1,117 @@
using System;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public abstract class Avistaz : HttpIndexerBase<AvistazSettings>
{
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override string BaseUrl => "";
protected virtual string LoginUrl => BaseUrl + "api/v1/jackett/auth";
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities();
private IIndexerRepository _indexerRepository;
public Avistaz(IIndexerRepository indexerRepository,
IHttpClient httpClient,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, indexerStatusService, configService, logger)
{
_indexerRepository = indexerRepository;
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
public override IParseIndexerResponse GetParser()
{
return new AvistazParser(Settings, Capabilities, BaseUrl);
}
protected virtual IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities();
return caps;
}
protected override void DoLogin()
{
Settings.Token = GetToken();
if (Definition.Id > 0)
{
_indexerRepository.UpdateSettings((IndexerDefinition)Definition);
}
_logger.Debug("Avistaz authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse response)
{
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.PreconditionFailed)
{
return true;
}
return false;
}
protected override ValidationFailure TestConnection()
{
try
{
GetToken();
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to connect to indexer");
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}
return null;
}
private string GetToken()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true
};
requestBuilder.Method = HttpMethod.POST;
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("pid", Settings.Pid.Trim())
.SetHeader("Content-Type", "application/json")
.Accept(HttpAccept.Json)
.Build();
var response = _httpClient.Post<AvistazAuthResponse>(authLoginRequest);
var token = response.Resource.Token;
return token;
}
}
}

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazRelease
{
public string Url { get; set; }
public string Download { get; set; }
[JsonProperty(PropertyName = "movie_tv")]
public AvistazIdInfo MovieTvinfo { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "file_name")]
public string FileName { get; set; }
[JsonProperty(PropertyName = "info_hash")]
public string InfoHash { get; set; }
public int Leech { get; set; }
public int Completed { get; set; }
public int Seed { get; set; }
[JsonProperty(PropertyName = "file_size")]
public int FileSize { get; set; }
[JsonProperty(PropertyName = "file_count")]
public int FileCount { get; set; }
[JsonProperty(PropertyName = "download_multiply")]
public double? DownloadMultiply { get; set; }
[JsonProperty(PropertyName = "upload_multiply")]
public double? UploadMultiply { get; set; }
[JsonProperty(PropertyName = "video_quality")]
public string VideoQuality { get; set; }
public string Type { get; set; }
}
public class AvistazResponse
{
public string Status { get; set; }
public List<AvistazRelease> Data { get; set; }
}
public class AvistazIdInfo
{
public int Tmdb { get; set; }
public int Tvdb { get; set; }
public string Imdb { get; set; }
public string Title { get; set; }
[JsonProperty(PropertyName = "tv_episode")]
public string TvEpisode { get; set; }
[JsonProperty(PropertyName = "tv_season")]
public string TVSeason { get; set; }
[JsonProperty(PropertyName = "tv_full_season")]
public bool TVFullSeason { get; set; }
}
public class AvistazAuthResponse
{
public string Token { get; set; }
public string Expiry { get; set; }
}
}

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazParser : IParseIndexerResponse
{
private readonly AvistazSettings _settings;
private readonly IndexerCapabilities _capabilities;
private readonly string _baseUrl;
private readonly HashSet<string> _hdResolutions = new HashSet<string> { "1080p", "1080i", "720p" };
public AvistazParser(AvistazSettings settings, IndexerCapabilities capabilities, string baseUrl)
{
_settings = settings;
_capabilities = capabilities;
_baseUrl = baseUrl;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
// Remove cookie cache
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
// Remove cookie cache
CookiesUpdater(null, null);
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<AvistazResponse>(indexerResponse.HttpResponse);
if (jsonResponse.Resource.Status != "success" ||
jsonResponse.Resource.Status.IsNullOrWhiteSpace())
{
return torrentInfos;
}
foreach (var row in jsonResponse.Resource.Data)
{
var details = row.Url;
var link = row.Download;
var cats = ParseCategories(row);
var release = new TorrentInfo
{
Title = row.FileName,
DownloadUrl = link,
InfoHash = row.InfoHash,
InfoUrl = details,
Guid = details,
Category = cats,
PublishDate = row.CreatedAt,
Size = row.FileSize,
Files = row.FileCount,
Grabs = row.Completed,
Seeders = row.Seed,
Peers = row.Leech + row.Seed,
ImdbId = ParseUtil.GetImdbID(row.MovieTvinfo.Imdb).Value,
TvdbId = row.MovieTvinfo.Tvdb,
TmdbId = row.MovieTvinfo.Tmdb,
DownloadVolumeFactor = row.DownloadMultiply,
UploadVolumeFactor = row.UploadMultiply,
MinimumRatio = 1,
MinimumSeedTime = 172800 // 48 hours
};
torrentInfos.Add(release);
}
// order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
}
// hook to adjust category parsing
protected virtual List<IndexerCategory> ParseCategories(AvistazRelease row)
{
var cats = new List<IndexerCategory>();
var resolution = row.VideoQuality;
switch (row.Type)
{
case "Movie":
cats.Add(resolution switch
{
var res when _hdResolutions.Contains(res) => NewznabStandardCategory.MoviesHD,
"2160p" => NewznabStandardCategory.MoviesUHD,
_ => NewznabStandardCategory.MoviesSD
});
break;
case "TV-Show":
cats.Add(resolution switch
{
var res when _hdResolutions.Contains(res) => NewznabStandardCategory.TVHD,
"2160p" => NewznabStandardCategory.TVUHD,
_ => NewznabStandardCategory.TVSD
});
break;
case "Music":
cats.Add(NewznabStandardCategory.Audio);
break;
default:
throw new Exception("Error parsing category!");
}
return cats;
}
}
}

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazRequestGenerator : IIndexerRequestGenerator
{
public AvistazSettings Settings { get; set; }
public string BaseUrl { get; set; }
public IDictionary<string, string> AuthCookieCache { get; set; }
public IHttpClient HttpClient { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public Logger Logger { get; set; }
protected virtual string SearchUrl => BaseUrl + "api/v1/jackett/torrents";
protected virtual bool ImdbInTags => false;
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
// hook to adjust the search category
protected virtual List<KeyValuePair<string, string>> GetBasicSearchParameters(int[] categories)
{
var categoryMapping = Capabilities.Categories.MapTorznabCapsToTrackers(categories).Distinct().ToList();
var qc = new List<KeyValuePair<string, string>> // NameValueCollection don't support cat[]=19&cat[]=6
{
{ "in", "1" },
{ "type", categoryMapping.Any() ? categoryMapping.First() : "0" }
};
// resolution filter to improve the search
if (!categories.Contains(NewznabStandardCategory.Movies.Id) && !categories.Contains(NewznabStandardCategory.TV.Id) &&
!categories.Contains(NewznabStandardCategory.Audio.Id))
{
if (categories.Contains(NewznabStandardCategory.MoviesUHD.Id) || categories.Contains(NewznabStandardCategory.TVUHD.Id))
{
qc.Add("video_quality[]", "6"); // 2160p
}
if (categories.Contains(NewznabStandardCategory.MoviesHD.Id) || categories.Contains(NewznabStandardCategory.TVHD.Id))
{
qc.Add("video_quality[]", "2"); // 720p
qc.Add("video_quality[]", "7"); // 1080i
qc.Add("video_quality[]", "3"); // 1080p
}
if (categories.Contains(NewznabStandardCategory.MoviesSD.Id) || categories.Contains(NewznabStandardCategory.TVSD.Id))
{
qc.Add("video_quality[]", "1"); // SD
}
}
return qc;
}
private IEnumerable<IndexerRequest> GetRequest(List<KeyValuePair<string, string>> searchParameters)
{
var searchUrl = SearchUrl + "?" + searchParameters.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
request.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.Token}");
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
parameters.Add("imdb", searchCriteria.ImdbId);
}
else
{
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
if (searchCriteria.ImdbId != null)
{
parameters.Add("imdb", searchCriteria.ImdbId);
}
else
{
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedTvSearchString).Trim());
}
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
throw new NotImplementedException();
}
// hook to adjust the search term
protected virtual string GetSearchTerm(string term) => term;
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.Categories);
parameters.Add("search", GetSearchTerm(searchCriteria.SanitizedSearchTerm).Trim());
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
}
}
}

@ -0,0 +1,42 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazSettingsValidator : AbstractValidator<AvistazSettings>
{
public AvistazSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class AvistazSettings : IProviderConfig
{
private static readonly AvistazSettingsValidator Validator = new AvistazSettingsValidator();
public AvistazSettings()
{
Token = "";
}
public string Token { get; set; }
[FieldDefinition(1, Label = "Username", HelpText = "Username", Privacy = PrivacyLevel.UserName)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(3, Label = "PID", HelpText = "PID from My Account or My Profile page")]
public string Pid { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -0,0 +1,63 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Avistaz;
namespace NzbDrone.Core.Indexers.Definitions
{
public class PrivateHD : Avistaz.Avistaz
{
public override string Name => "PrivateHD";
public override string BaseUrl => "https://privatehd.to/";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public PrivateHD(IIndexerRepository indexerRepository, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(indexerRepository, httpClient, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AvistazRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
Capabilities = Capabilities,
BaseUrl = BaseUrl
};
}
protected override IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesUHD);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesHD);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVUHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio);
return caps;
}
}
}

@ -6,6 +6,7 @@ namespace NzbDrone.Core.Indexers
{
public interface IIndexerRepository : IProviderRepository<IndexerDefinition>
{
void UpdateSettings(IndexerDefinition model);
}
public class IndexerRepository : ProviderRepository<IndexerDefinition>, IIndexerRepository
@ -14,5 +15,10 @@ namespace NzbDrone.Core.Indexers
: base(database, eventAggregator)
{
}
public void UpdateSettings(IndexerDefinition model)
{
SetFields(model, m => m.Settings);
}
}
}

Loading…
Cancel
Save