parent
0487309ee8
commit
ce2f322478
@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
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 Anidex : TorrentIndexerBase<AnidexSettings>
|
||||
{
|
||||
public override string Name => "Anidex";
|
||||
public override string[] IndexerUrls => new[] { "https://anidex.info/" };
|
||||
public override string Description => "Anidex is a Public torrent tracker and indexer, primarily for English fansub groups of anime";
|
||||
public override string Language => "en-US";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
public override IndexerCapabilities Capabilities => SetCapabilities();
|
||||
|
||||
public Anidex(IIndexerHttpClient httpClient,
|
||||
IEventAggregator eventAggregator,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new AnidexRequestGenerator(Settings, Capabilities);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new AnidexParser(Settings, Capabilities.Categories);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.TVAnime, "Anime - Sub");
|
||||
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.TVAnime, "Anime - Raw");
|
||||
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TVAnime, "Anime - Dub");
|
||||
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.TVAnime, "LA - Sub");
|
||||
caps.Categories.AddCategoryMapping("5", NewznabStandardCategory.TVAnime, "LA - Raw");
|
||||
caps.Categories.AddCategoryMapping("6", NewznabStandardCategory.BooksEBook, "Light Novel");
|
||||
caps.Categories.AddCategoryMapping("7", NewznabStandardCategory.BooksComics, "Manga - TLed");
|
||||
caps.Categories.AddCategoryMapping("8", NewznabStandardCategory.BooksComics, "Manga - Raw");
|
||||
caps.Categories.AddCategoryMapping("9", NewznabStandardCategory.AudioMP3, "♫ - Lossy");
|
||||
caps.Categories.AddCategoryMapping("10", NewznabStandardCategory.AudioLossless, "♫ - Lossless");
|
||||
caps.Categories.AddCategoryMapping("11", NewznabStandardCategory.AudioVideo, "♫ - Video");
|
||||
caps.Categories.AddCategoryMapping("12", NewznabStandardCategory.PCGames, "Games");
|
||||
caps.Categories.AddCategoryMapping("13", NewznabStandardCategory.PC0day, "Applications");
|
||||
caps.Categories.AddCategoryMapping("14", NewznabStandardCategory.XXXImageSet, "Pictures");
|
||||
caps.Categories.AddCategoryMapping("15", NewznabStandardCategory.XXX, "Adult Video");
|
||||
caps.Categories.AddCategoryMapping("16", NewznabStandardCategory.Other, "Other");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnidexRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
private readonly AnidexSettings _settings;
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
|
||||
public AnidexRequestGenerator(AnidexSettings settings, IndexerCapabilities capabilities)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return new IndexerPageableRequestChain();
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
|
||||
{
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "page", "search" },
|
||||
{ "s", "upload_timestamp" },
|
||||
{ "o", "desc" },
|
||||
{ "group_id", "0" }, // All groups
|
||||
{ "q", term ?? string.Empty }
|
||||
};
|
||||
|
||||
if (_settings.AuthorisedOnly)
|
||||
{
|
||||
parameters.Add("a", "1");
|
||||
}
|
||||
|
||||
var searchUrl = $"{_settings.BaseUrl}?{parameters.GetQueryString()}";
|
||||
|
||||
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
|
||||
|
||||
if (queryCats.Any())
|
||||
{
|
||||
searchUrl += "&id=" + string.Join(",", queryCats);
|
||||
}
|
||||
|
||||
if (_settings.LanguagesOnly.Any())
|
||||
{
|
||||
searchUrl += "&lang_id=" + string.Join(",", _settings.LanguagesOnly);
|
||||
}
|
||||
|
||||
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnidexParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly AnidexSettings _settings;
|
||||
private readonly IndexerCapabilitiesCategories _categories;
|
||||
|
||||
public AnidexParser(AnidexSettings settings, IndexerCapabilitiesCategories categories)
|
||||
{
|
||||
_settings = settings;
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Anidex search returned unexpected result. Expected 200 OK but got {indexerResponse.HttpResponse.StatusCode}.");
|
||||
}
|
||||
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var downloadUrl = _settings.BaseUrl + row.QuerySelector("a[href^=\"/dl/\"]")?.GetAttribute("href");
|
||||
var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(3) a")?.GetAttribute("href");
|
||||
|
||||
var title = row.QuerySelector("td:nth-child(3) span")?.GetAttribute("title")?.Trim();
|
||||
var language = row.QuerySelector("td:nth-child(1) img")?.GetAttribute("title")?.Trim();
|
||||
|
||||
if (language.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
title += $" [{language}]";
|
||||
}
|
||||
|
||||
var categoryLink = row.QuerySelector("td:nth-child(1) a").GetAttribute("href");
|
||||
var cat = ParseUtil.GetArgumentFromQueryString(categoryLink, "id");
|
||||
var categories = _categories.MapTrackerCatToNewznab(cat);
|
||||
|
||||
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent);
|
||||
var peers = seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(10)")?.TextContent.Trim());
|
||||
|
||||
var added = row.QuerySelector("td:nth-child(8)").GetAttribute("title").Trim();
|
||||
|
||||
var release = new TorrentInfo
|
||||
{
|
||||
Guid = infoUrl,
|
||||
InfoUrl = infoUrl,
|
||||
DownloadUrl = downloadUrl,
|
||||
MagnetUrl = row.QuerySelector("a[href^=\"magnet:?\"]")?.GetAttribute("href"),
|
||||
Title = title,
|
||||
Categories = categories,
|
||||
Seeders = seeders,
|
||||
Peers = peers,
|
||||
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)")?.TextContent.Trim()),
|
||||
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(11)")?.TextContent),
|
||||
PublishDate = DateTime.ParseExact(added, "yyyy-MM-dd HH:mm:ss UTC", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
releaseInfos.Add(release);
|
||||
}
|
||||
|
||||
return releaseInfos.ToArray();
|
||||
}
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
|
||||
public class AnidexSettings : NoAuthTorrentBaseSettings
|
||||
{
|
||||
public AnidexSettings()
|
||||
{
|
||||
AuthorisedOnly = false;
|
||||
LanguagesOnly = Array.Empty<int>();
|
||||
}
|
||||
|
||||
[FieldDefinition(2, Label = "Authorised Only", Type = FieldType.Checkbox, HelpText = "Search authorised torrents only")]
|
||||
public bool AuthorisedOnly { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Languages Only", Type = FieldType.Select, SelectOptions = typeof(AnidexLanguage), HelpText = "Search selected languages only. None ticked = ALL.")]
|
||||
public IEnumerable<int> LanguagesOnly { get; set; }
|
||||
}
|
||||
|
||||
public enum AnidexLanguage
|
||||
{
|
||||
[FieldOption(Hint = "English")]
|
||||
GB = 1,
|
||||
|
||||
[FieldOption(Hint = "Japanese")]
|
||||
JP = 2,
|
||||
|
||||
[FieldOption(Hint = "Polish")]
|
||||
PL = 3,
|
||||
|
||||
[FieldOption(Hint = "Serbo-Croatian")]
|
||||
RS = 4,
|
||||
|
||||
[FieldOption(Hint = "Dutch")]
|
||||
NL = 5,
|
||||
|
||||
[FieldOption(Hint = "Italian")]
|
||||
IT = 6,
|
||||
|
||||
[FieldOption(Hint = "Russian")]
|
||||
RU = 7,
|
||||
|
||||
[FieldOption(Hint = "German")]
|
||||
DE = 8,
|
||||
|
||||
[FieldOption(Hint = "Hungarian")]
|
||||
HU = 9,
|
||||
|
||||
[FieldOption(Hint = "French")]
|
||||
FR = 10,
|
||||
|
||||
[FieldOption(Hint = "Finnish")]
|
||||
FI = 11,
|
||||
|
||||
[FieldOption(Hint = "Vietnamese")]
|
||||
VN = 12,
|
||||
|
||||
[FieldOption(Hint = "Greek")]
|
||||
GR = 13,
|
||||
|
||||
[FieldOption(Hint = "Bulgarian")]
|
||||
BG = 14,
|
||||
|
||||
[FieldOption(Hint = "Spanish (Spain)")]
|
||||
ES = 15,
|
||||
|
||||
[FieldOption(Hint = "Portuguese (Brazil)")]
|
||||
BR = 16,
|
||||
|
||||
[FieldOption(Hint = "Portuguese (Portugal)")]
|
||||
PT = 17,
|
||||
|
||||
[FieldOption(Hint = "Swedish")]
|
||||
SE = 18,
|
||||
|
||||
[FieldOption(Hint = "Arabic")]
|
||||
SA = 19,
|
||||
|
||||
[FieldOption(Hint = "Danish")]
|
||||
DK = 20,
|
||||
|
||||
[FieldOption(Hint = "Chinese (Simplified)")]
|
||||
CN = 21,
|
||||
|
||||
[FieldOption(Hint = "Bengali")]
|
||||
BD = 22,
|
||||
|
||||
[FieldOption(Hint = "Romanian")]
|
||||
RO = 23,
|
||||
|
||||
[FieldOption(Hint = "Czech")]
|
||||
CZ = 24,
|
||||
|
||||
[FieldOption(Hint = "Mongolian")]
|
||||
MN = 25,
|
||||
|
||||
[FieldOption(Hint = "Turkish")]
|
||||
TR = 26,
|
||||
|
||||
[FieldOption(Hint = "Indonesian")]
|
||||
ID = 27,
|
||||
|
||||
[FieldOption(Hint = "Korean")]
|
||||
KR = 28,
|
||||
|
||||
[FieldOption(Hint = "Spanish (LATAM)")]
|
||||
MX = 29,
|
||||
|
||||
[FieldOption(Hint = "Persian")]
|
||||
IR = 30,
|
||||
|
||||
[FieldOption(Hint = "Malaysian")]
|
||||
MY = 31,
|
||||
}
|
||||
}
|
Loading…
Reference in new issue