mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
New: Add AnimeZ
This commit is contained in:
@@ -1,341 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Html.Parser;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public class AnimeTorrents : TorrentIndexerBase<AnimeTorrentsSettings>
|
||||
{
|
||||
public override string Name => "AnimeTorrents";
|
||||
public override string[] IndexerUrls => new[] { "https://animetorrents.me/" };
|
||||
public override string Description => "Definitive source for anime and manga";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
public override bool SupportsPagination => true;
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public AnimeTorrents(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimeTorrentsRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimeTorrentsParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
|
||||
{
|
||||
if (httpResponse.Content.Contains("Access Denied!") || httpResponse.Content.Contains("login.php"))
|
||||
{
|
||||
throw new IndexerAuthException("AnimeTorrents authentication with cookies failed.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IDictionary<string, string> GetCookies()
|
||||
{
|
||||
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Anime Movie");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesHD, "Anime Movie HD");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "Anime Series");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVAnime, "Anime Series HD");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.XXXDVD, "Hentai (censored)");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.XXXDVD, "Hentai (censored) HD");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.XXXDVD, "Hentai (un-censored)");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.XXXDVD, "Hentai (un-censored) HD");
|
||||
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.BooksForeign, "Light Novel");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksComics, "Manga");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.BooksComics, "Manga 18+");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.TVAnime, "OVA HD");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.BooksComics, "Doujin Anime");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXXDVD, "Doujin Anime 18+");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioForeign, "Doujin Music");
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.BooksComics, "Doujinshi");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.BooksComics, "Doujinshi 18+");
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.Audio, "OST");
|
||||
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeTorrentsRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly AnimeTorrentsSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public AnimeTorrentsRequestGenerator(AnimeTorrentsSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTerm = $"{searchCriteria.SanitizedSearchTerm}";
|
||||
|
||||
foreach (var category in GetTrackerCategories(searchTerm, searchCriteria))
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(searchTerm, category, searchCriteria));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTerm = $"{searchCriteria.SanitizedSearchTerm}";
|
||||
|
||||
foreach (var category in GetTrackerCategories(searchTerm, searchCriteria))
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(searchTerm, category, searchCriteria));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTerm = $"{searchCriteria.SanitizedSearchTerm}";
|
||||
|
||||
foreach (var category in GetTrackerCategories(searchTerm, searchCriteria))
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(searchTerm, category, searchCriteria));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTerm = $"{searchCriteria.SanitizedSearchTerm}";
|
||||
|
||||
foreach (var category in GetTrackerCategories(searchTerm, searchCriteria))
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(searchTerm, category, searchCriteria));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
var searchTerm = $"{searchCriteria.SanitizedSearchTerm}";
|
||||
|
||||
foreach (var category in GetTrackerCategories(searchTerm, searchCriteria))
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(searchTerm, category, searchCriteria));
|
||||
}
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, string category, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var searchUrl = _settings.BaseUrl + "ajax/torrents_data.php";
|
||||
|
||||
// replace non-word characters with % (wildcard)
|
||||
var searchString = Regex.Replace(term.Trim(), @"[\W]+", "%");
|
||||
|
||||
var page = searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0 ? (int)(searchCriteria.Offset / searchCriteria.Limit) + 1 : 1;
|
||||
|
||||
var refererUri = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/torrents.php")
|
||||
.AddQueryParam("cat", $"{category}");
|
||||
|
||||
if (_settings.DownloadableOnly)
|
||||
{
|
||||
refererUri = refererUri.AddQueryParam("dlable", "1");
|
||||
}
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(searchUrl)
|
||||
.AddQueryParam("total", "100") // Assuming the total number of pages
|
||||
.AddQueryParam("cat", $"{category}")
|
||||
.AddQueryParam("searchin", "filename")
|
||||
.AddQueryParam("search", searchString)
|
||||
.AddQueryParam("page", page)
|
||||
.SetHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.SetHeader("Referer", refererUri.FullUri)
|
||||
.Accept(HttpAccept.Html);
|
||||
|
||||
if (_settings.DownloadableOnly)
|
||||
{
|
||||
requestBuilder.AddQueryParam("dlable", "1");
|
||||
}
|
||||
|
||||
yield return new IndexerRequest(requestBuilder.Build());
|
||||
}
|
||||
|
||||
private List<string> GetTrackerCategories(string term, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var searchTerm = term.Trim();
|
||||
|
||||
var categoryMapping = _capabilities.Categories
|
||||
.MapTorznabCapsToTrackers(searchCriteria.Categories)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return searchTerm.IsNullOrWhiteSpace() && categoryMapping.Count == 2
|
||||
? categoryMapping
|
||||
: new List<string> { categoryMapping.FirstIfSingleOrDefault("0") };
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnimeTorrentsParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnimeTorrentsSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public AnimeTorrentsParser(AnimeTorrentsSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table tr");
|
||||
foreach (var (row, index) in rows.Skip(1).Select((v, i) => (v, i)))
|
||||
{
|
||||
var downloadVolumeFactor = row.QuerySelector("img[alt=\"Gold Torrent\"]") != null ? 0 : row.QuerySelector("img[alt=\"Silver Torrent\"]") != null ? 0.5 : 1;
|
||||
|
||||
// skip non-freeleech results when freeleech only is set
|
||||
if (_settings.FreeleechOnly && downloadVolumeFactor != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var qTitleLink = row.QuerySelector("td:nth-of-type(2) a:nth-of-type(1)");
|
||||
var title = qTitleLink?.TextContent.Trim();
|
||||
|
||||
// If we search and get no results, we still get a table just with no info.
|
||||
if (title.IsNullOrWhiteSpace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var infoUrl = qTitleLink?.GetAttribute("href");
|
||||
|
||||
// newbie users don't see DL links
|
||||
// use details link as placeholder
|
||||
// skipping the release prevents newbie users from adding the tracker (empty result)
|
||||
var downloadUrl = row.QuerySelector("td:nth-of-type(3) a")?.GetAttribute("href") ?? infoUrl;
|
||||
|
||||
var connections = row.QuerySelector("td:nth-of-type(8)").TextContent.Trim().Split('/', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
var seeders = ParseUtil.CoerceInt(connections[0]);
|
||||
var leechers = ParseUtil.CoerceInt(connections[1]);
|
||||
var grabs = ParseUtil.CoerceInt(connections[2]);
|
||||
|
||||
var categoryLink = row.QuerySelector("td:nth-of-type(1) a")?.GetAttribute("href") ?? string.Empty;
|
||||
var categoryId = ParseUtil.GetArgumentFromQueryString(categoryLink, "cat");
|
||||
|
||||
var publishedDate = DateTime.ParseExact(row.QuerySelector("td:nth-of-type(5)").TextContent, "dd MMM yy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
|
||||
if (publishedDate.Date == DateTime.Today)
|
||||
{
|
||||
publishedDate = publishedDate.Date + DateTime.Now.TimeOfDay - TimeSpan.FromMinutes(index);
|
||||
}
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = downloadUrl,
|
||||
Title = title,
|
||||
Categories = _categories.MapTrackerCatToNewznab(categoryId),
|
||||
PublishDate = publishedDate,
|
||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(6)").TextContent.Trim()),
|
||||
Seeders = seeders,
|
||||
Peers = leechers + seeders,
|
||||
Grabs = grabs,
|
||||
DownloadVolumeFactor = downloadVolumeFactor,
|
||||
UploadVolumeFactor = 1,
|
||||
Genres = row.QuerySelectorAll("td:nth-of-type(2) a.tortags").Select(t => t.TextContent.Trim()).ToList()
|
||||
};
|
||||
|
||||
var uploadFactor = row.QuerySelector("img[alt*=\"x Multiplier Torrent\"]")?.GetAttribute("alt");
|
||||
if (uploadFactor != null)
|
||||
{
|
||||
release.UploadVolumeFactor = ParseUtil.CoerceDouble(uploadFactor.Split('x')[0]);
|
||||
}
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnimeTorrentsSettings : CookieTorrentBaseSettings
|
||||
{
|
||||
public AnimeTorrentsSettings()
|
||||
{
|
||||
FreeleechOnly = false;
|
||||
DownloadableOnly = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Show freeleech torrents only")]
|
||||
public bool FreeleechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Downloadable Only", Type = FieldType.Checkbox, HelpText = "Search downloadable torrents only (enable this only if your account class is Newbie)", Advanced = true)]
|
||||
public bool DownloadableOnly { get; set; }
|
||||
}
|
||||
}
|
||||
156
src/NzbDrone.Core/Indexers/Definitions/AnimeZ.cs
Normal file
156
src/NzbDrone.Core/Indexers/Definitions/AnimeZ.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers.Definitions.Avistaz;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions;
|
||||
|
||||
public class AnimeZ : AvistazBase
|
||||
{
|
||||
public override string Name => "AnimeZ";
|
||||
public override string[] IndexerUrls => new[] { "https://animez.to/" };
|
||||
public override string Description => "AnimeZ (ex-AnimeTorrents) is a Private Torrent Tracker for ANIME / MANGA";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public AnimeZ(IIndexerRepository indexerRepository,
|
||||
IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(indexerRepository, httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnimeZRequestGenerator
|
||||
{
|
||||
Settings = Settings,
|
||||
Capabilities = Capabilities,
|
||||
PageSize = PageSize,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger
|
||||
};
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnimeZParser(Capabilities.Categories);
|
||||
}
|
||||
|
||||
public override async Task<IndexerDownloadResponse> Download(Uri link)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await base.Download(link).ConfigureAwait(false);
|
||||
}
|
||||
catch (ReleaseDownloadException ex) when (ex.InnerException is HttpException httpException &&
|
||||
httpException.Response.StatusCode is HttpStatusCode.Unauthorized)
|
||||
{
|
||||
await DoLogin().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await base.Download(link).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override Task<HttpRequest> GetDownloadRequest(Uri link)
|
||||
{
|
||||
var request = new HttpRequestBuilder(link.AbsoluteUri)
|
||||
.Accept(HttpAccept.Json)
|
||||
.SetHeader("Authorization", $"Bearer {Settings.Token}")
|
||||
.Build();
|
||||
|
||||
return Task.FromResult(request);
|
||||
}
|
||||
|
||||
protected override IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
LimitsDefault = PageSize,
|
||||
LimitsMax = PageSize,
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q,
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("TV", NewznabStandardCategory.TVAnime, "Anime > TV");
|
||||
caps.Categories.AddCategoryMapping("TV_SHORT", NewznabStandardCategory.TVAnime, "Anime > TV Short");
|
||||
caps.Categories.AddCategoryMapping("MOVIE", NewznabStandardCategory.Movies, "Anime > Movie");
|
||||
caps.Categories.AddCategoryMapping("SPECIAL", NewznabStandardCategory.TVAnime, "Anime > Special");
|
||||
caps.Categories.AddCategoryMapping("OVA", NewznabStandardCategory.TVAnime, "Anime > OVA");
|
||||
caps.Categories.AddCategoryMapping("ONA", NewznabStandardCategory.TVAnime, "Anime > ONA");
|
||||
caps.Categories.AddCategoryMapping("MUSIC", NewznabStandardCategory.TVAnime, "Anime > Music");
|
||||
caps.Categories.AddCategoryMapping("MANGA", NewznabStandardCategory.BooksComics, "Manga > Manga");
|
||||
caps.Categories.AddCategoryMapping("NOVEL", NewznabStandardCategory.BooksForeign, "Manga > Novel");
|
||||
caps.Categories.AddCategoryMapping("ONE_SHOT", NewznabStandardCategory.BooksForeign, "Manga > One-Shot");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeZRequestGenerator : AvistazRequestGenerator
|
||||
{
|
||||
protected override List<KeyValuePair<string, string>> GetBasicSearchParameters(SearchCriteriaBase searchCriteria, string genre = null)
|
||||
{
|
||||
var parameters = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
{ "limit", Math.Min(PageSize, searchCriteria.Limit.GetValueOrDefault(PageSize)).ToString() }
|
||||
};
|
||||
|
||||
var categoryMappings = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Distinct().ToList();
|
||||
|
||||
if (categoryMappings.Any())
|
||||
{
|
||||
foreach (var category in categoryMappings)
|
||||
{
|
||||
parameters.Add("format[]", category);
|
||||
}
|
||||
}
|
||||
|
||||
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
|
||||
{
|
||||
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
|
||||
parameters.Add("page", page.ToString());
|
||||
}
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
parameters.Add("freeleech", "1");
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimeZParser(IndexerCapabilitiesCategories categories) : AvistazParserBase
|
||||
{
|
||||
protected override List<IndexerCategory> ParseCategories(AvistazRelease row)
|
||||
{
|
||||
return categories.MapTrackerCatToNewznab(row.Format).ToList();
|
||||
}
|
||||
|
||||
protected override string ParseTitle(AvistazRelease row)
|
||||
{
|
||||
return row.ReleaseTitle.IsNotNullOrWhiteSpace() ? row.ReleaseTitle : row.FileName;
|
||||
}
|
||||
}
|
||||
@@ -5,72 +5,77 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
public class AvistazRelease
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Download { get; set; }
|
||||
public Dictionary<string, string> Category { get; set; }
|
||||
public string Url { get; init; }
|
||||
public string Download { get; init; }
|
||||
public Dictionary<string, string> Category { get; init; }
|
||||
|
||||
[JsonPropertyName("movie_tv")]
|
||||
public AvistazIdInfo MovieTvinfo { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
public AvistazIdInfo MovieTvinfo { get; init; }
|
||||
|
||||
[JsonPropertyName("created_at_iso")]
|
||||
public string CreatedAtIso { get; set; }
|
||||
public string CreatedAtIso { get; init; }
|
||||
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
public string FileName { get; init; }
|
||||
|
||||
[JsonPropertyName("release_title")]
|
||||
public string ReleaseTitle { get; init; }
|
||||
|
||||
[JsonPropertyName("info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
public int? Leech { get; set; }
|
||||
public int? Completed { get; set; }
|
||||
public int? Seed { get; set; }
|
||||
public string InfoHash { get; init; }
|
||||
|
||||
public int? Leech { get; init; }
|
||||
public int? Completed { get; init; }
|
||||
public int? Seed { get; init; }
|
||||
|
||||
[JsonPropertyName("file_size")]
|
||||
public long? FileSize { get; set; }
|
||||
public long? FileSize { get; init; }
|
||||
|
||||
[JsonPropertyName("file_count")]
|
||||
public int? FileCount { get; set; }
|
||||
public int? FileCount { get; init; }
|
||||
|
||||
[JsonPropertyName("download_multiply")]
|
||||
public double? DownloadMultiply { get; set; }
|
||||
public double? DownloadMultiply { get; init; }
|
||||
|
||||
[JsonPropertyName("upload_multiply")]
|
||||
public double? UploadMultiply { get; set; }
|
||||
public double? UploadMultiply { get; init; }
|
||||
|
||||
[JsonPropertyName("video_quality")]
|
||||
public string VideoQuality { get; set; }
|
||||
public string Type { get; set; }
|
||||
public List<AvistazLanguage> Audio { get; set; }
|
||||
public List<AvistazLanguage> Subtitle { get; set; }
|
||||
public string VideoQuality { get; init; }
|
||||
|
||||
public string Type { get; init; }
|
||||
|
||||
public string Format { get; init; }
|
||||
|
||||
public IReadOnlyCollection<AvistazLanguage> Audio { get; init; }
|
||||
public IReadOnlyCollection<AvistazLanguage> Subtitle { get; init; }
|
||||
}
|
||||
|
||||
public class AvistazLanguage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Language { get; set; }
|
||||
public int Id { get; init; }
|
||||
public string Language { get; init; }
|
||||
}
|
||||
|
||||
public class AvistazResponse
|
||||
{
|
||||
public List<AvistazRelease> Data { get; set; }
|
||||
public IReadOnlyCollection<AvistazRelease> Data { get; init; }
|
||||
}
|
||||
|
||||
public class AvistazErrorResponse
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Message { get; init; }
|
||||
}
|
||||
|
||||
public class AvistazIdInfo
|
||||
{
|
||||
public string Tmdb { get; set; }
|
||||
public string Tvdb { get; set; }
|
||||
public string Imdb { get; set; }
|
||||
public string Tmdb { get; init; }
|
||||
public string Tvdb { get; init; }
|
||||
public string Imdb { get; init; }
|
||||
}
|
||||
|
||||
public class AvistazAuthResponse
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string Token { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
@@ -21,7 +20,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(6);
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
|
||||
private IIndexerRepository _indexerRepository;
|
||||
private readonly IIndexerRepository _indexerRepository;
|
||||
|
||||
public AvistazBase(IIndexerRepository indexerRepository,
|
||||
IIndexerHttpClient httpClient,
|
||||
@@ -57,7 +56,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
try
|
||||
{
|
||||
Settings.Token = await GetToken();
|
||||
Settings.Token = await GetToken().ConfigureAwait(false);
|
||||
|
||||
if (Definition.Id > 0)
|
||||
{
|
||||
@@ -66,7 +65,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
_logger.Debug("Avistaz authentication succeeded.");
|
||||
}
|
||||
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
catch (HttpException ex) when (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to authenticate with Avistaz");
|
||||
|
||||
@@ -90,11 +89,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
try
|
||||
{
|
||||
await GetToken();
|
||||
await GetToken().ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
_logger.Warn(ex, "Unauthorized request to indexer");
|
||||
|
||||
@@ -110,10 +109,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
_logger.Warn(ex, "Unable to connect to indexer");
|
||||
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details");
|
||||
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
|
||||
}
|
||||
|
||||
return await base.TestConnection();
|
||||
return await base.TestConnection().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<string> GetToken()
|
||||
@@ -121,18 +120,17 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
var requestBuilder = new HttpRequestBuilder(LoginUrl)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
|
||||
// TODO: Change to HttpAccept.Json after they fix the issue with missing headers
|
||||
var authLoginRequest = requestBuilder
|
||||
.Post()
|
||||
.AddFormParameter("username", Settings.Username)
|
||||
.AddFormParameter("password", Settings.Password)
|
||||
.AddFormParameter("pid", Settings.Pid.Trim())
|
||||
.Accept(HttpAccept.Html)
|
||||
.Accept(HttpAccept.Json)
|
||||
.Build();
|
||||
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
var response = await ExecuteAuth(authLoginRequest).ConfigureAwait(false);
|
||||
|
||||
if (!STJson.TryDeserialize<AvistazAuthResponse>(response.Content, out var authResponse))
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return releaseInfos.ToArray();
|
||||
return releaseInfos;
|
||||
}
|
||||
|
||||
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
@@ -52,31 +52,28 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
|
||||
foreach (var row in jsonResponse.Data)
|
||||
{
|
||||
var details = row.Url;
|
||||
var link = row.Download;
|
||||
|
||||
var cats = ParseCategories(row);
|
||||
var detailsUrl = row.Url;
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Title = row.FileName,
|
||||
DownloadUrl = link,
|
||||
Guid = detailsUrl,
|
||||
InfoUrl = detailsUrl,
|
||||
Title = ParseTitle(row),
|
||||
DownloadUrl = row.Download,
|
||||
Categories = ParseCategories(row).ToList(),
|
||||
InfoHash = row.InfoHash,
|
||||
InfoUrl = details,
|
||||
Guid = details,
|
||||
Categories = cats,
|
||||
PublishDate = DateTime.Parse(row.CreatedAtIso, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
Size = row.FileSize,
|
||||
Files = row.FileCount,
|
||||
Grabs = row.Completed,
|
||||
Seeders = row.Seed,
|
||||
Peers = row.Leech + row.Seed,
|
||||
PublishDate = DateTime.Parse(row.CreatedAtIso, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
DownloadVolumeFactor = row.DownloadMultiply,
|
||||
UploadVolumeFactor = row.UploadMultiply,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 259200, // 72 hours
|
||||
Languages = row.Audio?.Select(x => x.Language).ToList() ?? new List<string>(),
|
||||
Subs = row.Subtitle?.Select(x => x.Language).ToList() ?? new List<string>()
|
||||
Languages = row.Audio?.Select(x => x.Language).ToList() ?? [],
|
||||
Subs = row.Subtitle?.Select(x => x.Language).ToList() ?? []
|
||||
};
|
||||
|
||||
if (row.FileSize is > 0)
|
||||
@@ -90,54 +87,57 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
};
|
||||
}
|
||||
|
||||
if (row.MovieTvinfo != null)
|
||||
if (row.MovieTvinfo is not null)
|
||||
{
|
||||
release.ImdbId = ParseUtil.GetImdbId(row.MovieTvinfo.Imdb).GetValueOrDefault();
|
||||
release.TmdbId = row.MovieTvinfo.Tmdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tmdb, out var tmdbResult) ? tmdbResult : 0;
|
||||
release.TvdbId = row.MovieTvinfo.Tvdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tvdb, out var tvdbResult) ? tvdbResult : 0;
|
||||
release.TmdbId = row.MovieTvinfo.Tmdb.IsNotNullOrWhiteSpace() && ParseUtil.TryCoerceInt(row.MovieTvinfo.Tmdb, out var tmdbResult) ? tmdbResult : 0;
|
||||
release.TvdbId = row.MovieTvinfo.Tvdb.IsNotNullOrWhiteSpace() && ParseUtil.TryCoerceInt(row.MovieTvinfo.Tvdb, out var tvdbResult) ? tvdbResult : 0;
|
||||
}
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
// order by date
|
||||
return releaseInfos
|
||||
.OrderByDescending(o => o.PublishDate)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
// hook to adjust category parsing
|
||||
protected virtual List<IndexerCategory> ParseCategories(AvistazRelease row)
|
||||
protected virtual IReadOnlyList<IndexerCategory> ParseCategories(AvistazRelease row)
|
||||
{
|
||||
var cats = new List<IndexerCategory>();
|
||||
var resolution = row.VideoQuality;
|
||||
var categories = new List<IndexerCategory>();
|
||||
var videoQuality = row.VideoQuality;
|
||||
|
||||
switch (row.Type)
|
||||
switch (row.Type.ToUpperInvariant())
|
||||
{
|
||||
case "Movie":
|
||||
cats.Add(resolution switch
|
||||
case "MOVIE":
|
||||
categories.Add(videoQuality switch
|
||||
{
|
||||
var res when _hdResolutions.Contains(res) => NewznabStandardCategory.MoviesHD,
|
||||
"2160p" => NewznabStandardCategory.MoviesUHD,
|
||||
_ => NewznabStandardCategory.MoviesSD
|
||||
});
|
||||
break;
|
||||
case "TV-Show":
|
||||
cats.Add(resolution switch
|
||||
case "TV-SHOW":
|
||||
categories.Add(videoQuality switch
|
||||
{
|
||||
var res when _hdResolutions.Contains(res) => NewznabStandardCategory.TVHD,
|
||||
"2160p" => NewznabStandardCategory.TVUHD,
|
||||
_ => NewznabStandardCategory.TVSD
|
||||
});
|
||||
break;
|
||||
case "Music":
|
||||
cats.Add(NewznabStandardCategory.Audio);
|
||||
case "MUSIC":
|
||||
categories.Add(NewznabStandardCategory.Audio);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Error parsing Avistaz category type {row.Type}");
|
||||
throw new Exception($"Error parsing Avistaz category type \"{row.Type}\"");
|
||||
}
|
||||
|
||||
return cats;
|
||||
return categories;
|
||||
}
|
||||
|
||||
protected virtual string ParseTitle(AvistazRelease row)
|
||||
{
|
||||
return row.FileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +82,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
var searchUrl = SearchUrl + "?" + searchParameters.GetQueryString();
|
||||
|
||||
// TODO: Change to HttpAccept.Json after they fix the issue with missing headers
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
request.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.Token}");
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
|
||||
request.HttpRequest.Headers.Set("Authorization", $"Bearer {Settings.Token}");
|
||||
|
||||
request.HttpRequest.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
|
||||
request.HttpRequest.SuppressHttpErrorStatusCodes = [HttpStatusCode.NotFound];
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
private static readonly AvistazSettingsValidator Validator = new();
|
||||
|
||||
public AvistazSettings()
|
||||
{
|
||||
Token = "";
|
||||
FreeleechOnly = false;
|
||||
}
|
||||
|
||||
public string Token { get; set; }
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
[FieldDefinition(2, Label = "Username", HelpText = "IndexerAvistazSettingsUsernameHelpText", HelpTextWarning = "IndexerAvistazSettingsUsernameHelpTextWarning", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public override string Name => "PrivateHD";
|
||||
public override string[] IndexerUrls => new[] { "https://privatehd.to/" };
|
||||
public override string Description => "PrivateHD (PHD) is a Private Torrent Tracker for HD MOVIES / TV and the sister-site of AvistaZ, CinemaZ, ExoticaZ, and AnimeTorrents";
|
||||
public override string Description => "PrivateHD (PHD) is a Private Torrent Tracker for HD MOVIES / TV and the sister-site of AvistaZ, CinemaZ, ExoticaZ, and AnimeZ";
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
|
||||
|
||||
public PrivateHD(IIndexerRepository indexerRepository,
|
||||
|
||||
Reference in New Issue
Block a user