From b3b0467d221f70657904c4a819246e1e2347ba85 Mon Sep 17 00:00:00 2001 From: Ashino Date: Sat, 24 Jul 2021 03:09:57 +0200 Subject: [PATCH] New: (Indexer) Torrent Xthor (#342) * New: (Indexer) Torrent Xthor * New: (Indexer) Torrent Xthor Fix multiple lines fieldDefinition and texts * New: (Indexer) Torrent Xthor Remove unwanted blankline * New: (Indexer) Torrent Xthor Fix BaseSettings after rebase * New: (Indexer) Torrent Xthor * New: (Indexer) Torrent Xthor Fix multiple lines fieldDefinition and texts * New: (Indexer) Torrent Xthor Remove unwanted blankline * New: (Indexer) Torrent Xthor Fix BaseSettings after rebase * New: (Indexer) Torrent Xthor - Add "EnhancedFrenchAccent" field that will allow to find VF2 releases when searching VFF or VFQ - Fix BaseSettings - Decrease the RateLimit to 2.1 seconds - Remove page argument when searching page 0 * Fix punctuation * New: (Indexer) Torrent Xthor * New: (Indexer) Torrent Xthor Fix multiple lines fieldDefinition and texts * New: (Indexer) Torrent Xthor Remove unwanted blankline * New: (Indexer) Torrent Xthor Fix BaseSettings after rebase * New: (Indexer) Torrent Xthor - Add "EnhancedFrenchAccent" field that will allow to find VF2 releases when searching VFF or VFQ - Fix BaseSettings - Decrease the RateLimit to 2.1 seconds - Remove page argument when searching page 0 * Fix punctuation * New: (Indexer) Torrent Xthor - Fix "unknown" categories when searching with Prowlarr (category conversion was missing) - Now every class has its own file (like all others indexers) * New: (Indexer) Torrent Xthor - Fix fielddefinition to be on single line Co-authored-by: TCLE --- .../Indexers/Definitions/Xthor/Xthor.cs | 124 +++++++++++++ .../Indexers/Definitions/Xthor/XthorParser.cs | 121 +++++++++++++ .../Definitions/Xthor/XthorProperties.cs | 167 ++++++++++++++++++ .../Xthor/XthorRequestGenerator.cs | 145 +++++++++++++++ .../Definitions/Xthor/XthorResponse.cs | 71 ++++++++ .../Definitions/Xthor/XthorSettings.cs | 65 +++++++ .../Xthor/XthorSettingsValidator.cs | 8 + 7 files changed, 701 insertions(+) create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/Xthor.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorParser.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorProperties.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorRequestGenerator.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorResponse.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettings.cs create mode 100644 src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettingsValidator.cs diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/Xthor.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/Xthor.cs new file mode 100644 index 000000000..c5f1419ea --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/Xthor.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class Xthor : TorrentIndexerBase + { + public override string Name => "Xthor"; + public override string[] IndexerUrls => new string[] { "https://api.xthor.tk/" }; + public override string Language => "fr-fr"; + public override string Description => "Xthor is a general Private torrent site"; + public override Encoding Encoding => Encoding.GetEncoding("windows-1252"); + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override IndexerPrivacy Privacy => IndexerPrivacy.Private; + + public override TimeSpan RateLimit => TimeSpan.FromSeconds(2.1); + public override IndexerCapabilities Capabilities => SetCapabilities(); + + public Xthor(IHttpClient httpClient, + IEventAggregator eventAggregator, + IIndexerStatusService indexerStatusService, + IConfigService configService, + Logger logger) + : base(httpClient, eventAggregator, indexerStatusService, configService, logger) + { + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new XthorRequestGenerator() { Settings = Settings, Capabilities = Capabilities }; + } + + public override IParseIndexerResponse GetParser() + { + return new XthorParser(Settings, Capabilities.Categories); + } + + private IndexerCapabilities SetCapabilities() + { + var caps = new IndexerCapabilities + { + TvSearchParams = new List { TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep }, + MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.TmdbId }, + MusicSearchParams = new List { MusicSearchParam.Q }, + BookSearchParams = new List { BookSearchParam.Q } + }; + + caps.Categories.AddCategoryMapping(118, NewznabStandardCategory.MoviesBluRay, "Films 2160p/Bluray"); + caps.Categories.AddCategoryMapping(119, NewznabStandardCategory.MoviesBluRay, "Films 2160p/Remux"); + caps.Categories.AddCategoryMapping(107, NewznabStandardCategory.MoviesUHD, "Films 2160p/x265"); + caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesBluRay, "Films 1080p/BluRay"); + caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.MoviesBluRay, "Films 1080p/Remux"); + caps.Categories.AddCategoryMapping(100, NewznabStandardCategory.MoviesHD, "Films 1080p/x265"); + caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.MoviesHD, "Films 1080p/x264"); + caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.MoviesHD, "Films 720p/x264"); + caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.MoviesSD, "Films SD/x264"); + caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Movies3D, "Films 3D"); + caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.MoviesSD, "Films XviD"); + caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.MoviesDVD, "Films DVD"); + caps.Categories.AddCategoryMapping(122, NewznabStandardCategory.MoviesHD, "Films HDTV"); + caps.Categories.AddCategoryMapping(94, NewznabStandardCategory.MoviesWEBDL, "Films WEBDL"); + caps.Categories.AddCategoryMapping(95, NewznabStandardCategory.MoviesWEBDL, "Films WEBRiP"); + caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.TVDocumentary, "Films Documentaire"); + caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.MoviesOther, "Films Animation"); + caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.MoviesOther, "Films Spectacle"); + caps.Categories.AddCategoryMapping(125, NewznabStandardCategory.TVSport, "Films Sports"); + caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.AudioVideo, "Films Concerts, Clips"); + caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.MoviesOther, "Films VOSTFR"); + + // TV / Series + caps.Categories.AddCategoryMapping(104, NewznabStandardCategory.TVOther, "Series BluRay"); + caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.TVOther, "Series Pack VF"); + caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.TVHD, "Series HD VF"); + caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TVSD, "Series SD VF"); + caps.Categories.AddCategoryMapping(98, NewznabStandardCategory.TVOther, "Series Pack VOSTFR"); + caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.TVHD, "Series HD VOSTFR"); + caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.TVSD, "Series SD VOSTFR"); + caps.Categories.AddCategoryMapping(101, NewznabStandardCategory.TVAnime, "Series Packs Anime"); + caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.TVAnime, "Series Animes"); + caps.Categories.AddCategoryMapping(110, NewznabStandardCategory.TVAnime, "Series Anime VOSTFR"); + caps.Categories.AddCategoryMapping(123, NewznabStandardCategory.TVOther, "Series Animation"); + caps.Categories.AddCategoryMapping(109, NewznabStandardCategory.TVDocumentary, "Series DOC"); + caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.TVOther, "Series Sport"); + caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVOther, "Series Emission TV"); + + // XxX / MISC + caps.Categories.AddCategoryMapping(36, NewznabStandardCategory.XXX, "MISC XxX/Films"); + caps.Categories.AddCategoryMapping(105, NewznabStandardCategory.XXX, "MISC XxX/Séries"); + caps.Categories.AddCategoryMapping(114, NewznabStandardCategory.XXX, "MISC XxX/Lesbiennes"); + caps.Categories.AddCategoryMapping(115, NewznabStandardCategory.XXX, "MISC XxX/Gays"); + caps.Categories.AddCategoryMapping(113, NewznabStandardCategory.XXX, "MISC XxX/Hentai"); + caps.Categories.AddCategoryMapping(120, NewznabStandardCategory.XXX, "MISC XxX/Magazines"); + + // Books / Livres + caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.BooksEBook, "Livres Romans"); + caps.Categories.AddCategoryMapping(124, NewznabStandardCategory.AudioAudiobook, "Livres Audio Books"); + caps.Categories.AddCategoryMapping(96, NewznabStandardCategory.BooksMags, "Livres Magazines"); + caps.Categories.AddCategoryMapping(99, NewznabStandardCategory.BooksOther, "Livres Bandes dessinées"); + caps.Categories.AddCategoryMapping(116, NewznabStandardCategory.BooksEBook, "Livres Romans Jeunesse"); + caps.Categories.AddCategoryMapping(102, NewznabStandardCategory.BooksComics, "Livres Comics"); + caps.Categories.AddCategoryMapping(103, NewznabStandardCategory.BooksOther, "Livres Mangas"); + + // SOFTWARE / Logiciels + caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCGames, "Logiciels Jeux PC"); + caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.ConsolePS3, "Logiciels Playstation"); + caps.Categories.AddCategoryMapping(111, NewznabStandardCategory.PCMac, "Logiciels Jeux MAC"); + caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.ConsoleXBox360, "Logiciels XboX"); + caps.Categories.AddCategoryMapping(112, NewznabStandardCategory.PC, "Logiciels Jeux Linux"); + caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.ConsoleWii, "Logiciels Nintendo"); + caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.ConsoleNDS, "Logiciels NDS"); + caps.Categories.AddCategoryMapping(117, NewznabStandardCategory.PC, "Logiciels ROM"); + caps.Categories.AddCategoryMapping(21, NewznabStandardCategory.PC, "Logiciels Applis PC"); + caps.Categories.AddCategoryMapping(22, NewznabStandardCategory.PCMac, "Logiciels Applis Mac"); + caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.PCMobileAndroid, "Logiciels Smartphone"); + + return caps; + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorParser.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorParser.cs new file mode 100644 index 000000000..66dc4af51 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorParser.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class XthorParser : IParseIndexerResponse + { + private readonly XthorSettings _settings; + private readonly IndexerCapabilitiesCategories _categories; + private string _torrentDetailsUrl; + + public XthorParser(XthorSettings settings, IndexerCapabilitiesCategories categories) + { + _settings = settings; + _categories = categories; + _torrentDetailsUrl = _settings.BaseUrl + "details.php?id={id}"; + } + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var torrentInfos = new List(); + var contentString = indexerResponse.Content; + var xthorResponse = JsonConvert.DeserializeObject(contentString); + + if (xthorResponse != null) + { + CheckApiState(xthorResponse.Error); + + // If contains torrents + if (xthorResponse.Torrents != null) + { + // Adding each torrent row to releases + // Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407 + torrentInfos.AddRange(xthorResponse.Torrents + .Where(torrent => torrent.Category != 106).Select(torrent => + { + if (_settings.NeedMultiReplacement) + { + var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])"); + torrent.Name = regex.Replace(torrent.Name, + "$1" + _settings.MultiReplacement + "$2"); + } + + // issue #8759 replace vostfr and subfrench with English + if (!string.IsNullOrEmpty(_settings.SubReplacement)) + { + torrent.Name = torrent.Name.Replace("VOSTFR", _settings.SubReplacement) + .Replace("SUBFRENCH", _settings.SubReplacement); + } + + var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.Added); + + var guid = new string(_torrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); + var details = new string(_torrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); + var link = new string(torrent.Download_link); + var release = new TorrentInfo + { + // Mapping data + Categories = _categories.MapTrackerCatToNewznab(torrent.Category.ToString()), + Title = torrent.Name, + Seeders = torrent.Seeders, + Peers = torrent.Seeders + torrent.Leechers, + MinimumRatio = 1, + MinimumSeedTime = 345600, + PublishDate = publishDate, + Size = torrent.Size, + Grabs = torrent.Times_completed, + Files = torrent.Numfiles, + UploadVolumeFactor = 1, + DownloadVolumeFactor = torrent.Freeleech == 1 ? 0 : 1, + Guid = guid, + InfoUrl = details, + DownloadUrl = link, + TmdbId = torrent.Tmdb_id + }; + + return release; + })); + } + } + + return torrentInfos.ToArray(); + } + + private void CheckApiState(XthorError state) + { + // Switch on state + switch (state.Code) + { + case 0: + // Everything OK + break; + case 1: + // Passkey not found + throw new Exception("Passkey not found in tracker's database"); + case 2: + // No results + break; + case 3: + // Power Saver + break; + case 4: + // DDOS Attack, API disabled + throw new Exception("Tracker is under DDOS attack, API disabled"); + case 8: + // AntiSpam Protection + throw new Exception("Triggered AntiSpam Protection, please delay your requests!"); + default: + // Unknown state + throw new Exception("Unknown state, aborting querying"); + } + } + + public Action, DateTime?> CookiesUpdater { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorProperties.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorProperties.cs new file mode 100644 index 000000000..31b439de6 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorProperties.cs @@ -0,0 +1,167 @@ +using NzbDrone.Core.Annotations; + +// ReSharper disable InconsistentNaming +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public enum XthorAccent + { + [FieldOption(Hint = "All Voices (default)")] + All = 0, + + [FieldOption(Hint = "Françaises")] + VFF = 1, + + [FieldOption(Hint = "Quebecoises")] + VFQ = 2, + + [FieldOption(Hint = "Françaises et Québécoises")] + VF2 = 47, + + [FieldOption(Hint = "Anglaises")] + EN = 3, + + [FieldOption(Hint = "Japonaises")] + JP = 4, + + [FieldOption(Hint = "Espagnoles")] + ES = 5, + + [FieldOption(Hint = "Allemandes")] + DE = 6, + + [FieldOption(Hint = "Chinoises")] + CH = 7, + + [FieldOption(Hint = "Italiennes")] + IT = 8, + + [FieldOption(Hint = "Coréennes")] + KR = 9, + + [FieldOption(Hint = "Danoises")] + DK = 10, + + [FieldOption(Hint = "Russes")] + RU = 11, + + [FieldOption(Hint = "Portugaises")] + PT = 12, + + [FieldOption(Hint = "Hindi")] + IN = 13, + + [FieldOption(Hint = "Hollandaises")] + NL = 14, + + [FieldOption(Hint = "Suédoises")] + SE = 15, + + [FieldOption(Hint = "Norvégiennes")] + NO = 16, + + [FieldOption(Hint = "Thaïlandaises")] + TH = 17, + + [FieldOption(Hint = "Hébreu")] + HE = 18, + + [FieldOption(Hint = "Persanes")] + PE = 19, + + [FieldOption(Hint = "Arabes")] + AR = 20, + + [FieldOption(Hint = "Turques")] + TR = 21, + + [FieldOption(Hint = "Hongroises")] + HU = 22, + + [FieldOption(Hint = "Polonaises")] + PL = 23, + + [FieldOption(Hint = "Finnoises")] + FI = 24, + + [FieldOption(Hint = "Indonésiennes")] + ID = 25, + + [FieldOption(Hint = "Roumaines")] + RO = 26, + + [FieldOption(Hint = "Malaisiennes")] + MY = 27, + + [FieldOption(Hint = "Estoniennes")] + EE = 28, + + [FieldOption(Hint = "Islandaises")] + IS = 29, + + [FieldOption(Hint = "Grecques")] + GR = 30, + + [FieldOption(Hint = "Serbes")] + RS = 31, + + [FieldOption(Hint = "Norvégiennes (2)")] + NOB = 32, + + [FieldOption(Hint = "Ukrainiennes")] + UA = 33, + + [FieldOption(Hint = "Bulgares")] + BG = 34, + + [FieldOption(Hint = "Tagalogues")] + PH = 35, + + [FieldOption(Hint = "Xhosa")] + XH = 36, + + [FieldOption(Hint = "Kurdes")] + KU = 37, + + [FieldOption(Hint = "Bengali")] + BE = 38, + + [FieldOption(Hint = "Amhariques")] + AM = 39, + + [FieldOption(Hint = "Bosniaques")] + BO = 40, + + [FieldOption(Hint = "Malayalam")] + MA = 41, + + [FieldOption(Hint = "Télougou")] + TE = 42, + + [FieldOption(Hint = "Bambara")] + BA = 43, + + [FieldOption(Hint = "Catalanes")] + CT = 44, + + [FieldOption(Hint = "Tchèques")] + CZ = 45, + + [FieldOption(Hint = "Afrikaans")] + AF = 46, + } + + public enum XthorPagesNumber + { + [FieldOption(Label = "1", Hint = "1 (32 results - default / best perf.)")] + one = 1, + + [FieldOption(Label = "2", Hint = "2 (64 results)")] + two = 2, + + [FieldOption(Label = "3", Hint = "3 (96 results)")] + three = 3, + + [FieldOption(Label = "4", Hint = "4 (128 results - hard limit max)")] + four = 4, + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorRequestGenerator.cs new file mode 100644 index 000000000..ea991d83d --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorRequestGenerator.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text.RegularExpressions; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class XthorRequestGenerator : IIndexerRequestGenerator + { + public XthorSettings Settings { get; set; } + public IndexerCapabilities Capabilities { get; set; } + + public XthorRequestGenerator() + { + } + + private IEnumerable GetPagedRequests(string term, + int[] categories, + int pageNumber, + string tmdbid = null, + int forced_accent = 0) + { + var searchUrl = string.Format("{0}", Settings.BaseUrl.TrimEnd('/')); + + var searchString = term; + + var trackerCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories) ?? new List(); + + var queryCollection = new NameValueCollection(); + + queryCollection.Add("passkey", Settings.Passkey); + + if (tmdbid.IsNotNullOrWhiteSpace()) + { + queryCollection.Add("tmdbid", tmdbid); + } + else if (!string.IsNullOrWhiteSpace(searchString)) + { + searchString = searchString.Replace("'", ""); // ignore ' (e.g. search for america's Next Top Model) + if (Settings.EnhancedAnime && + (trackerCats.Contains("101") || trackerCats.Contains("32") || trackerCats.Contains("110"))) + { + var regex = new Regex(" ([0-9]+)"); + searchString = regex.Replace(searchString, " E$1"); + } + + queryCollection.Add("search", searchString); + } + + if (Settings.FreeleechOnly) + { + queryCollection.Add("freeleech", "1"); + } + + if (trackerCats.Count >= 1) + { + queryCollection.Add("category", string.Join("+", trackerCats)); + } + + if (Settings.Accent >= 1 && forced_accent == 0) + { + queryCollection.Add("accent", Settings.Accent.ToString()); + } + + if (forced_accent != 0) + { + queryCollection.Add("accent", forced_accent.ToString()); + } + + if (pageNumber > 0) + { + queryCollection.Add("page", pageNumber.ToString()); + } + + searchUrl = searchUrl + "?" + queryCollection.GetQueryString(); + + var request = new IndexerRequest(searchUrl, HttpAccept.Html); + + yield return request; + } + + public IndexerPageableRequestChain GetSearchRequestsCommon(SearchCriteriaBase searchCriteria, + string searchTerm, + string tmdbid = null) + { + var pageableRequests = new IndexerPageableRequestChain(); + var actualPage = 0; + + while (actualPage < Settings.MaxPages) + { + pageableRequests.Add(GetPagedRequests(searchTerm, searchCriteria.Categories, actualPage, tmdbid)); + + if (Settings.EnhancedFrenchAccent && (Settings.Accent == 1 || Settings.Accent == 2)) + { + pageableRequests.Add( + GetPagedRequests(searchTerm, searchCriteria.Categories, actualPage, tmdbid, 47)); + } + + if (tmdbid.IsNotNullOrWhiteSpace() && Settings.ByPassPageForTmDbid) + { + break; + } + + ++actualPage; + } + + return pageableRequests; + } + + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + return GetSearchRequestsCommon(searchCriteria, + string.Format("{0}", searchCriteria.SanitizedSearchTerm), + searchCriteria.TmdbId.ToString()); + } + + public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria) + { + return GetSearchRequestsCommon(searchCriteria, string.Format("{0}", searchCriteria.SanitizedSearchTerm)); + } + + public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) + { + return GetSearchRequestsCommon(searchCriteria, + string.Format("{0}", searchCriteria.SanitizedTvSearchString)); + } + + public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria) + { + return GetSearchRequestsCommon(searchCriteria, string.Format("{0}", searchCriteria.SanitizedSearchTerm)); + } + + public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria) + { + return GetSearchRequestsCommon(searchCriteria, string.Format("{0}", searchCriteria.SanitizedSearchTerm)); + } + + public Func> GetCookies { get; set; } + public Action, DateTime?> CookiesUpdater { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorResponse.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorResponse.cs new file mode 100644 index 000000000..46fcab285 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorResponse.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class XthorResponse + { + public XthorError Error { get; set; } + public XthorUser User { get; set; } + public List Torrents { get; set; } + } + + /// + /// State of API + /// + public class XthorError + { + public int Code { get; set; } + public string Descr { get; set; } + } + + /// + /// User Informations + /// + public class XthorUser + { + public int Id { get; set; } + public string Username { get; set; } + public long Uploaded { get; set; } + public long Downloaded { get; set; } + public int Uclass { get; set; } // Class is a reserved keyword. + public decimal Bonus_point { get; set; } + public int Hits_and_run { get; set; } + public string Avatar_url { get; set; } + } + + /// + /// Torrent Informations + /// + public class XthorTorrent + { + public int Id { get; set; } + public int Category { get; set; } + public int Seeders { get; set; } + public int Leechers { get; set; } + public string Name { get; set; } + public int Times_completed { get; set; } + public long Size { get; set; } + public int Added { get; set; } + public int Freeleech { get; set; } + public int Numfiles { get; set; } + public string Release_group { get; set; } + public string Download_link { get; set; } + public int Tmdb_id { get; set; } + + public override string ToString() => string.Format( + "[XthorTorrent: id={0}, category={1}, seeders={2}, leechers={3}, name={4}, times_completed={5}, size={6}, added={7}, freeleech={8}, numfiles={9}, release_group={10}, download_link={11}, tmdb_id={12}]", + Id, + Category, + Seeders, + Leechers, + Name, + Times_completed, + Size, + Added, + Freeleech, + Numfiles, + Release_group, + Download_link, + Tmdb_id); + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettings.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettings.cs new file mode 100644 index 000000000..8c7e40f4b --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettings.cs @@ -0,0 +1,65 @@ +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class XthorSettings : IIndexerSettings + { + private static readonly XthorSettingsValidator Validator = new XthorSettingsValidator(); + + public XthorSettings() + { + BaseUrl = "https://api.xthor.tk/"; + Passkey = ""; + FreeleechOnly = false; + Accent = 0; + EnhancedFrenchAccent = false; + NeedMultiReplacement = false; + MultiReplacement = ""; + SubReplacement = ""; + EnhancedAnime = true; + ByPassPageForTmDbid = true; + MaxPages = 1; + } + + [FieldDefinition(1, Label = "Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")] + public string BaseUrl { get; set; } + + [FieldDefinition(2, Label = "Passkey", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Passkey")] + public string Passkey { get; set; } + + [FieldDefinition(3, Label = "Freeleech only", Privacy = PrivacyLevel.Normal, Type = FieldType.Checkbox, HelpText = "If you want to discover only freeleech torrents to not impact your ratio, check the related box.")] + public bool FreeleechOnly { get; set; } + + [FieldDefinition(4, Label = "Specific language", Type = FieldType.Select, SelectOptions = typeof(XthorAccent), HelpText = "You can scope your searches with a specific language / accent.")] + public int Accent { get; set; } + + [FieldDefinition(5, Label = "Do you want to use enhanced FRENCH search?", Type = FieldType.Checkbox, HelpText = "If you search for VFF or VFQ accent, it will also search with VFF+VFQ accent.")] + public bool EnhancedFrenchAccent { get; set; } + + [FieldDefinition(6, Label = "Replace MULTI keyword", Type = FieldType.Checkbox, HelpText = "Useful if you want MULTI release to be parsed as another language")] + public bool NeedMultiReplacement { get; set; } + + [FieldDefinition(7, Label = "MULTI replacement", Type = FieldType.Textbox, HelpText = "Word used to replace \"MULTI\" keyword in release title")] + public string MultiReplacement { get; set; } + + [FieldDefinition(8, Label = "SUB replacement", Type = FieldType.Textbox, HelpText = "Do you want to replace \"VOSTFR\" and \"SUBFRENCH\" with specific word?")] + public string SubReplacement { get; set; } + + [FieldDefinition(9, Label = "Do you want to use enhanced ANIME search?", Type = FieldType.Checkbox, HelpText = "if you have \"Anime\", this will improve queries made to this tracker related to this type when making searches. (This will change the episode number to EXXX)")] + public bool EnhancedAnime { get; set; } + + [FieldDefinition(10, Label = "Do you want to bypass max pages for TMDB searches? (Radarr) - Hard limit of 4", Type = FieldType.Checkbox, HelpText = "(recommended) this indexer is compatible with TMDB queries (for movies only), so when requesting content with an TMDB ID, we will search directly ID on API. Results will be more accurate, so you can enable a max pages bypass for this query type.", Advanced = true)] + public bool ByPassPageForTmDbid { get; set; } + + [FieldDefinition(11, Label = "How many pages do you want to follow?", Type = FieldType.Select, SelectOptions = typeof(XthorPagesNumber), HelpText = "(not recommended) you can increase max pages to follow when making a request. But be aware that this API is very buggy on tracker side, most of time, results of next pages are same as the first page. Even if we deduplicate rows, you will loose performance for the same results.", Advanced = true)] + public int MaxPages { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + + public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettingsValidator.cs b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettingsValidator.cs new file mode 100644 index 000000000..d0bd64afc --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/Xthor/XthorSettingsValidator.cs @@ -0,0 +1,8 @@ +using FluentValidation; + +namespace NzbDrone.Core.Indexers.Definitions.Xthor +{ + public class XthorSettingsValidator : AbstractValidator + { + } +}