diff --git a/src/NzbDrone.Core/Indexers/Definitions/AnimeTorrents.cs b/src/NzbDrone.Core/Indexers/Definitions/AnimeTorrents.cs deleted file mode 100644 index 14b64979b..000000000 --- a/src/NzbDrone.Core/Indexers/Definitions/AnimeTorrents.cs +++ /dev/null @@ -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 - { - 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 GetCookies() - { - return CookieUtil.CookieHeaderToDictionary(Settings.Cookie); - } - - private IndexerCapabilities SetCapabilities() - { - var caps = new IndexerCapabilities - { - TvSearchParams = new List - { - TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep - }, - MovieSearchParams = new List - { - 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 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 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 { categoryMapping.FirstIfSingleOrDefault("0") }; - } - - public Func> GetCookies { get; set; } - public Action, 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 ParseResponse(IndexerResponse indexerResponse) - { - var releaseInfos = new List(); - - 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, 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; } - } -} diff --git a/src/NzbDrone.Core/Indexers/Definitions/AnimeZ.cs b/src/NzbDrone.Core/Indexers/Definitions/AnimeZ.cs new file mode 100644 index 000000000..c8b9ce55f --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/AnimeZ.cs @@ -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 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 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.Q, TvSearchParam.Season, TvSearchParam.Ep + }, + MovieSearchParams = new List + { + MovieSearchParam.Q + }, + BookSearchParams = new List + { + 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> GetBasicSearchParameters(SearchCriteriaBase searchCriteria, string genre = null) + { + var parameters = new List> + { + { "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 ParseCategories(AvistazRelease row) + { + return categories.MapTrackerCatToNewznab(row.Format).ToList(); + } + + protected override string ParseTitle(AvistazRelease row) + { + return row.ReleaseTitle.IsNotNullOrWhiteSpace() ? row.ReleaseTitle : row.FileName; + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazApi.cs b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazApi.cs index 74f115368..754ae013a 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazApi.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazApi.cs @@ -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 Category { get; set; } + public string Url { get; init; } + public string Download { get; init; } + public Dictionary 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 Audio { get; set; } - public List Subtitle { get; set; } + public string VideoQuality { get; init; } + + public string Type { get; init; } + + public string Format { get; init; } + + public IReadOnlyCollection Audio { get; init; } + public IReadOnlyCollection 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 Data { get; set; } + public IReadOnlyCollection 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; } } } diff --git a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazBase.cs b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazBase.cs index b14bac702..fb1b43276 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazBase.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazBase.cs @@ -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 @@ public abstract class AvistazBase : TorrentIndexerBase 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 @@ protected override async Task DoLogin() { try { - Settings.Token = await GetToken(); + Settings.Token = await GetToken().ConfigureAwait(false); if (Definition.Id > 0) { @@ -66,7 +65,7 @@ protected override async Task DoLogin() _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 @@ protected override async Task TestConnection() { 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 @@ protected override async Task TestConnection() { _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 GetToken() @@ -121,18 +120,17 @@ private async Task GetToken() 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(response.Content, out var authResponse)) { diff --git a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazParserBase.cs b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazParserBase.cs index 6411c858f..31255aa8c 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazParserBase.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazParserBase.cs @@ -24,7 +24,7 @@ public IList ParseResponse(IndexerResponse indexerResponse) if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound) { - return releaseInfos.ToArray(); + return releaseInfos; } if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests) @@ -52,31 +52,28 @@ public IList ParseResponse(IndexerResponse indexerResponse) 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(), - Subs = row.Subtitle?.Select(x => x.Language).ToList() ?? new List() + 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 @@ public IList ParseResponse(IndexerResponse indexerResponse) }; } - 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 ParseCategories(AvistazRelease row) + protected virtual IReadOnlyList ParseCategories(AvistazRelease row) { - var cats = new List(); - var resolution = row.VideoQuality; + var categories = new List(); + 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; } } } diff --git a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazRequestGenerator.cs index 6513cc913..5265fdd11 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazRequestGenerator.cs @@ -82,11 +82,10 @@ private IEnumerable GetRequest(List { 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; } diff --git a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazSettings.cs b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazSettings.cs index bb431c47b..e67cf7c7f 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazSettings.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Avistaz/AvistazSettings.cs @@ -19,13 +19,7 @@ public class AvistazSettings : NoAuthTorrentBaseSettings { 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; } diff --git a/src/NzbDrone.Core/Indexers/Definitions/PrivateHD.cs b/src/NzbDrone.Core/Indexers/Definitions/PrivateHD.cs index 21fc76ede..c6ec1c471 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/PrivateHD.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/PrivateHD.cs @@ -10,7 +10,7 @@ public class PrivateHD : AvistazBase { 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,