2020-02-09 02:35:16 +00:00
|
|
|
using System;
|
2017-04-15 08:45:10 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Specialized;
|
2020-05-03 23:35:52 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2017-04-15 08:45:10 +00:00
|
|
|
using System.Linq;
|
2017-11-05 09:42:03 +00:00
|
|
|
using System.Net;
|
2017-08-13 18:49:03 +00:00
|
|
|
using System.Text;
|
2019-01-11 11:45:54 +00:00
|
|
|
using System.Text.RegularExpressions;
|
2017-04-15 08:45:10 +00:00
|
|
|
using System.Threading.Tasks;
|
2018-03-10 08:05:56 +00:00
|
|
|
using Jackett.Common.Models;
|
|
|
|
using Jackett.Common.Models.IndexerConfig.Bespoke;
|
|
|
|
using Jackett.Common.Services.Interfaces;
|
|
|
|
using Jackett.Common.Utils;
|
|
|
|
using Jackett.Common.Utils.Clients;
|
2017-04-15 08:45:10 +00:00
|
|
|
using Newtonsoft.Json;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using NLog;
|
2020-06-11 15:09:27 +00:00
|
|
|
using WebRequest = Jackett.Common.Utils.Clients.WebRequest;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2018-03-10 08:05:56 +00:00
|
|
|
namespace Jackett.Common.Indexers
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2020-05-03 23:35:52 +00:00
|
|
|
[ExcludeFromCodeCoverage]
|
2017-07-10 20:58:44 +00:00
|
|
|
public class Xthor : BaseCachingWebIndexer
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2019-10-20 18:09:41 +00:00
|
|
|
private static string ApiEndpoint => "https://api.xthor.tk/";
|
2021-05-04 16:38:37 +00:00
|
|
|
private string TorrentDetailsUrl => SiteLink + "details.php?id={id}";
|
|
|
|
private string ReplaceMulti => ConfigData.ReplaceMulti.Value;
|
|
|
|
private bool EnhancedAnime => ConfigData.EnhancedAnime.Value;
|
|
|
|
private static int MaxPageLoads => 4;
|
2017-11-29 18:18:08 +00:00
|
|
|
|
2020-10-19 21:19:10 +00:00
|
|
|
public override string[] LegacySiteLinks { get; protected set; } = {
|
2017-11-29 18:18:08 +00:00
|
|
|
"https://xthor.bz/",
|
2020-10-19 21:19:10 +00:00
|
|
|
"https://xthor.to"
|
2017-11-29 18:18:08 +00:00
|
|
|
};
|
2017-04-15 08:45:10 +00:00
|
|
|
private ConfigurationDataXthor ConfigData => (ConfigurationDataXthor)configData;
|
|
|
|
|
2020-12-11 22:14:21 +00:00
|
|
|
public Xthor(IIndexerConfigurationService configService, Utils.Clients.WebClient w, Logger l,
|
|
|
|
IProtectionService ps, ICacheService cs)
|
2020-05-11 19:59:28 +00:00
|
|
|
: base(id: "xthor",
|
|
|
|
name: "Xthor",
|
|
|
|
description: "General French Private Tracker",
|
|
|
|
link: "https://xthor.tk/",
|
2020-10-20 00:38:27 +00:00
|
|
|
caps: new TorznabCapabilities
|
|
|
|
{
|
2020-10-18 20:47:36 +00:00
|
|
|
TvSearchParams = new List<TvSearchParam>
|
|
|
|
{
|
|
|
|
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
|
|
|
},
|
|
|
|
MovieSearchParams = new List<MovieSearchParam>
|
|
|
|
{
|
|
|
|
MovieSearchParam.Q
|
2020-10-20 00:38:27 +00:00
|
|
|
},
|
|
|
|
MusicSearchParams = new List<MusicSearchParam>
|
|
|
|
{
|
|
|
|
MusicSearchParam.Q
|
|
|
|
},
|
|
|
|
BookSearchParams = new List<BookSearchParam>
|
|
|
|
{
|
|
|
|
BookSearchParam.Q
|
2020-10-18 20:47:36 +00:00
|
|
|
}
|
2020-10-18 17:26:22 +00:00
|
|
|
},
|
2020-05-11 19:59:28 +00:00
|
|
|
configService: configService,
|
|
|
|
client: w,
|
|
|
|
logger: l,
|
|
|
|
p: ps,
|
2020-12-11 22:14:21 +00:00
|
|
|
cacheService: cs,
|
2020-05-11 19:59:28 +00:00
|
|
|
downloadBase: "https://xthor.tk/download.php?torrent=",
|
|
|
|
configData: new ConfigurationDataXthor())
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
Encoding = Encoding.UTF8;
|
|
|
|
Language = "fr-fr";
|
|
|
|
Type = "private";
|
|
|
|
|
2021-05-03 00:38:36 +00:00
|
|
|
// Api has 1req/2s limit
|
|
|
|
webclient.requestDelay = 2.1;
|
|
|
|
|
2020-12-06 21:37:49 +00:00
|
|
|
// Movies / Films
|
|
|
|
AddCategoryMapping(118, TorznabCatType.MoviesBluRay, "Films 2160p/Bluray");
|
|
|
|
AddCategoryMapping(119, TorznabCatType.MoviesBluRay, "Films 2160p/Remux");
|
|
|
|
AddCategoryMapping(107, TorznabCatType.MoviesUHD, "Films 2160p/x265");
|
|
|
|
AddCategoryMapping(1, TorznabCatType.MoviesBluRay, "Films 1080p/BluRay");
|
|
|
|
AddCategoryMapping(2, TorznabCatType.MoviesBluRay, "Films 1080p/Remux");
|
|
|
|
AddCategoryMapping(100, TorznabCatType.MoviesHD, "Films 1080p/x265");
|
|
|
|
AddCategoryMapping(4, TorznabCatType.MoviesHD, "Films 1080p/x264");
|
|
|
|
AddCategoryMapping(5, TorznabCatType.MoviesHD, "Films 720p/x264");
|
|
|
|
AddCategoryMapping(7, TorznabCatType.MoviesSD, "Films SD/x264");
|
|
|
|
AddCategoryMapping(3, TorznabCatType.Movies3D, "Films 3D");
|
|
|
|
AddCategoryMapping(6, TorznabCatType.MoviesSD, "Films XviD");
|
|
|
|
AddCategoryMapping(8, TorznabCatType.MoviesDVD, "Films DVD");
|
|
|
|
AddCategoryMapping(122, TorznabCatType.MoviesHD, "Films HDTV");
|
|
|
|
AddCategoryMapping(94, TorznabCatType.MoviesWEBDL, "Films WEBDL");
|
|
|
|
AddCategoryMapping(95, TorznabCatType.MoviesWEBDL, "Films WEBRiP");
|
|
|
|
AddCategoryMapping(12, TorznabCatType.TVDocumentary, "Films Documentaire");
|
|
|
|
AddCategoryMapping(31, TorznabCatType.MoviesOther, "Films Animation");
|
|
|
|
AddCategoryMapping(33, TorznabCatType.MoviesOther, "Films Spectacle");
|
|
|
|
AddCategoryMapping(125, TorznabCatType.TVSport, "Films Sports");
|
|
|
|
AddCategoryMapping(20, TorznabCatType.AudioVideo, "Films Concerts, Clips");
|
|
|
|
AddCategoryMapping(9, TorznabCatType.MoviesOther, "Films VOSTFR");
|
2019-09-22 18:41:34 +00:00
|
|
|
|
2020-12-06 21:37:49 +00:00
|
|
|
// TV / Series
|
|
|
|
AddCategoryMapping(104, TorznabCatType.TVOther, "Series BluRay");
|
|
|
|
AddCategoryMapping(13, TorznabCatType.TVOther, "Series Pack VF");
|
|
|
|
AddCategoryMapping(15, TorznabCatType.TVHD, "Series HD VF");
|
|
|
|
AddCategoryMapping(14, TorznabCatType.TVSD, "Series SD VF");
|
|
|
|
AddCategoryMapping(98, TorznabCatType.TVOther, "Series Pack VOSTFR");
|
|
|
|
AddCategoryMapping(17, TorznabCatType.TVHD, "Series HD VOSTFR");
|
|
|
|
AddCategoryMapping(16, TorznabCatType.TVSD, "Series SD VOSTFR");
|
|
|
|
AddCategoryMapping(101, TorznabCatType.TVAnime, "Series Packs Anime");
|
|
|
|
AddCategoryMapping(32, TorznabCatType.TVAnime, "Series Animes");
|
|
|
|
AddCategoryMapping(110, TorznabCatType.TVAnime, "Series Anime VOSTFR");
|
|
|
|
AddCategoryMapping(123, TorznabCatType.TVOther, "Series Animation");
|
|
|
|
AddCategoryMapping(109, TorznabCatType.TVDocumentary, "Series DOC");
|
|
|
|
AddCategoryMapping(34, TorznabCatType.TVOther, "Series Sport");
|
|
|
|
AddCategoryMapping(30, TorznabCatType.TVOther, "Series Emission TV");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2020-12-06 21:37:49 +00:00
|
|
|
// XxX / MISC
|
|
|
|
AddCategoryMapping(36, TorznabCatType.XXX, "MISC XxX/Films");
|
|
|
|
AddCategoryMapping(105, TorznabCatType.XXX, "MISC XxX/Séries");
|
|
|
|
AddCategoryMapping(114, TorznabCatType.XXX, "MISC XxX/Lesbiennes");
|
|
|
|
AddCategoryMapping(115, TorznabCatType.XXX, "MISC XxX/Gays");
|
|
|
|
AddCategoryMapping(113, TorznabCatType.XXX, "MISC XxX/Hentai");
|
|
|
|
AddCategoryMapping(120, TorznabCatType.XXX, "MISC XxX/Magazines");
|
2019-09-22 18:41:34 +00:00
|
|
|
|
2020-12-06 21:37:49 +00:00
|
|
|
// Books / Livres
|
|
|
|
AddCategoryMapping(24, TorznabCatType.BooksEBook, "Livres Romans");
|
|
|
|
AddCategoryMapping(124, TorznabCatType.AudioAudiobook, "Livres Audio Books");
|
|
|
|
AddCategoryMapping(96, TorznabCatType.BooksMags, "Livres Magazines");
|
|
|
|
AddCategoryMapping(99, TorznabCatType.BooksOther, "Livres Bandes dessinées");
|
|
|
|
AddCategoryMapping(116, TorznabCatType.BooksEBook, "Livres Romans Jeunesse");
|
|
|
|
AddCategoryMapping(102, TorznabCatType.BooksComics, "Livres Comics");
|
|
|
|
AddCategoryMapping(103, TorznabCatType.BooksOther, "Livres Mangas");
|
2019-01-10 14:54:28 +00:00
|
|
|
|
2020-12-06 21:37:49 +00:00
|
|
|
// SOFTWARE / Logiciels
|
|
|
|
AddCategoryMapping(25, TorznabCatType.PCGames, "Logiciels Jeux PC");
|
|
|
|
AddCategoryMapping(27, TorznabCatType.ConsolePS3, "Logiciels Playstation");
|
|
|
|
AddCategoryMapping(111, TorznabCatType.PCMac, "Logiciels Jeux MAC");
|
|
|
|
AddCategoryMapping(26, TorznabCatType.ConsoleXBox360, "Logiciels XboX");
|
|
|
|
AddCategoryMapping(112, TorznabCatType.PC, "Logiciels Jeux Linux");
|
|
|
|
AddCategoryMapping(28, TorznabCatType.ConsoleWii, "Logiciels Nintendo");
|
|
|
|
AddCategoryMapping(29, TorznabCatType.ConsoleNDS, "Logiciels NDS");
|
|
|
|
AddCategoryMapping(117, TorznabCatType.PC, "Logiciels ROM");
|
|
|
|
AddCategoryMapping(21, TorznabCatType.PC, "Logiciels Applis PC");
|
|
|
|
AddCategoryMapping(22, TorznabCatType.PCMac, "Logiciels Applis Mac");
|
|
|
|
AddCategoryMapping(23, TorznabCatType.PCMobileAndroid, "Logiciels Smartphone");
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Configure our Provider
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="configJson">Our params in Json</param>
|
|
|
|
/// <returns>Configuration state</returns>
|
2020-02-25 16:08:03 +00:00
|
|
|
|
|
|
|
// Warning 1998 is async method with no await calls inside
|
|
|
|
// TODO: Remove pragma by wrapping return in Task.FromResult and removing async
|
2020-03-26 22:15:28 +00:00
|
|
|
|
2017-07-10 20:58:44 +00:00
|
|
|
#pragma warning disable 1998
|
2017-10-29 06:50:47 +00:00
|
|
|
|
2017-06-28 05:31:38 +00:00
|
|
|
public override async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
2017-07-10 20:58:44 +00:00
|
|
|
#pragma warning restore 1998
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
// Provider not yet configured
|
|
|
|
IsConfigured = false;
|
|
|
|
|
|
|
|
// Retrieve config values set by Jackett's user
|
|
|
|
LoadValuesFromJson(configJson);
|
|
|
|
|
|
|
|
// Check & Validate Config
|
|
|
|
ValidateConfig();
|
|
|
|
|
|
|
|
// Tracker is now configured
|
|
|
|
IsConfigured = true;
|
|
|
|
|
|
|
|
// Saving data
|
|
|
|
SaveConfig();
|
|
|
|
|
|
|
|
return IndexerConfigurationStatus.RequiresTesting;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Execute our search query
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="query">Query</param>
|
|
|
|
/// <returns>Releases</returns>
|
2017-07-03 05:15:47 +00:00
|
|
|
protected override async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
var releases = new List<ReleaseInfo>();
|
2021-05-04 16:38:37 +00:00
|
|
|
var searchTerm = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
|
2018-04-11 16:15:15 +00:00
|
|
|
searchTerm = searchTerm.Trim();
|
2017-08-10 06:23:19 +00:00
|
|
|
searchTerm = searchTerm.ToLower();
|
2021-05-04 16:38:37 +00:00
|
|
|
searchTerm = searchTerm.Replace(" ", ".");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2019-10-22 03:54:26 +00:00
|
|
|
if (EnhancedAnime && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110)))
|
2019-01-11 11:45:54 +00:00
|
|
|
{
|
2020-02-10 22:16:19 +00:00
|
|
|
var regex = new Regex(" ([0-9]+)");
|
2019-01-11 11:45:54 +00:00
|
|
|
searchTerm = regex.Replace(searchTerm, " E$1");
|
|
|
|
}
|
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Info("\nXthor - Search requested for \"" + searchTerm + "\"");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
// Multiple page support
|
|
|
|
var nextPage = 1; var followingPages = true;
|
|
|
|
do
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
// Build our query
|
|
|
|
var request = BuildQuery(searchTerm, query, ApiEndpoint, nextPage);
|
|
|
|
|
|
|
|
// Getting results
|
|
|
|
logger.Info("\nXthor - Querying API page " + nextPage);
|
|
|
|
var results = await QueryTrackerAsync(request);
|
|
|
|
|
|
|
|
// Torrents Result Count
|
|
|
|
var torrentsCount = 0;
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
try
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2021-05-04 16:38:37 +00:00
|
|
|
// Deserialize our Json Response
|
|
|
|
var xthorResponse = JsonConvert.DeserializeObject<XthorResponse>(results);
|
|
|
|
|
|
|
|
// Check Tracker's State
|
|
|
|
CheckApiState(xthorResponse.Error);
|
|
|
|
|
|
|
|
// If contains torrents
|
|
|
|
if (xthorResponse.Torrents != null)
|
|
|
|
{
|
|
|
|
// Store torrents rows count result
|
|
|
|
torrentsCount = xthorResponse.Torrents.Count();
|
|
|
|
logger.Info("\nXthor - Found " + torrentsCount + " torrents on current page.");
|
|
|
|
|
|
|
|
// Adding each torrent row to releases
|
|
|
|
// Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407
|
|
|
|
releases.AddRange(xthorResponse.Torrents
|
|
|
|
.Where(torrent => torrent.Category != 106).Select(torrent =>
|
|
|
|
{
|
|
|
|
//issue #3847 replace multi keyword
|
|
|
|
if (!string.IsNullOrEmpty(ReplaceMulti))
|
|
|
|
{
|
|
|
|
var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])");
|
|
|
|
torrent.Name = regex.Replace(torrent.Name, "$1" + ReplaceMulti + "$2");
|
|
|
|
}
|
|
|
|
|
|
|
|
// issue #8759 replace vostfr and subfrench with English
|
|
|
|
if (ConfigData.Vostfr.Value)
|
|
|
|
torrent.Name = torrent.Name.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH");
|
|
|
|
|
|
|
|
var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.Added);
|
|
|
|
//TODO replace with download link?
|
|
|
|
var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString()));
|
|
|
|
var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString()));
|
|
|
|
var link = new Uri(torrent.Download_link);
|
|
|
|
var release = new ReleaseInfo
|
|
|
|
{
|
|
|
|
// Mapping data
|
|
|
|
Category = 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,
|
|
|
|
Details = details,
|
|
|
|
Link = link,
|
|
|
|
TMDb = torrent.Tmdb_id
|
|
|
|
};
|
|
|
|
|
|
|
|
return release;
|
|
|
|
}));
|
|
|
|
nextPage++;
|
|
|
|
}
|
|
|
|
else
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Info("\nXthor - No results found on page " + (nextPage -1) + ", stopping follow of next page.");
|
|
|
|
// No results or no more results available
|
|
|
|
followingPages = false;
|
|
|
|
}
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
2021-05-04 16:38:37 +00:00
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
OnParseError("Unable to parse result \n" + ex.StackTrace, ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop ?
|
|
|
|
if(nextPage > MaxPageLoads | torrentsCount < 32 | string.IsNullOrWhiteSpace(searchTerm))
|
|
|
|
{
|
|
|
|
logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page limit or max available results reached or indexer test.");
|
|
|
|
followingPages = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (followingPages);
|
2017-04-15 08:45:10 +00:00
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
// Check if there is duplicate and return unique rows - Xthor API can be very buggy !
|
|
|
|
var uniqReleases = releases.GroupBy(x => x.Guid).Select(x => x.First()).ToList();
|
2017-04-15 08:45:10 +00:00
|
|
|
// Return found releases
|
2021-05-04 16:38:37 +00:00
|
|
|
return uniqReleases;
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Response from Tracker's API
|
|
|
|
/// </summary>
|
|
|
|
public class XthorResponse
|
|
|
|
{
|
2021-05-03 00:38:36 +00:00
|
|
|
public XthorError Error { get; set; }
|
|
|
|
public XthorUser User { get; set; }
|
|
|
|
public List<XthorTorrent> Torrents { get; set; }
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// State of API
|
|
|
|
/// </summary>
|
|
|
|
public class XthorError
|
|
|
|
{
|
2021-05-03 00:38:36 +00:00
|
|
|
public int Code { get; set; }
|
|
|
|
public string Descr { get; set; }
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// User Informations
|
|
|
|
/// </summary>
|
|
|
|
public class XthorUser
|
|
|
|
{
|
2021-05-03 00:38:36 +00:00
|
|
|
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; }
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Torrent Informations
|
|
|
|
/// </summary>
|
|
|
|
public class XthorTorrent
|
|
|
|
{
|
2021-05-03 00:38:36 +00:00
|
|
|
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);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Build query to process
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="term">Term to search</param>
|
|
|
|
/// <param name="query">Torznab Query for categories mapping</param>
|
|
|
|
/// <param name="url">Search url for provider</param>
|
|
|
|
/// <returns>URL to query for parsing and processing results</returns>
|
2021-05-04 16:38:37 +00:00
|
|
|
private string BuildQuery(string term, TorznabQuery query, string url, int page = 1)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
var parameters = new NameValueCollection();
|
|
|
|
var categoriesList = MapTorznabCapsToTrackers(query);
|
|
|
|
|
|
|
|
// Passkey
|
|
|
|
parameters.Add("passkey", ConfigData.PassKey.Value);
|
|
|
|
|
|
|
|
// If search term provided
|
|
|
|
if (!string.IsNullOrWhiteSpace(term))
|
|
|
|
{
|
|
|
|
// Add search term
|
|
|
|
// ReSharper disable once AssignNullToNotNullAttribute
|
2017-11-05 09:42:03 +00:00
|
|
|
parameters.Add("search", WebUtility.UrlEncode(term));
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
parameters.Add("search", string.Empty);
|
2021-05-04 16:38:37 +00:00
|
|
|
// Showing all torrents
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Loop on Categories needed
|
2017-04-20 15:06:28 +00:00
|
|
|
if (categoriesList.Count > 0)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
2017-12-04 20:38:21 +00:00
|
|
|
parameters.Add("category", string.Join("+", categoriesList));
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If Only Freeleech Enabled
|
|
|
|
if (ConfigData.Freeleech.Value)
|
|
|
|
{
|
|
|
|
parameters.Add("freeleech", "1");
|
|
|
|
}
|
|
|
|
|
2019-09-22 18:41:34 +00:00
|
|
|
if (!string.IsNullOrEmpty(ConfigData.Accent.Value))
|
|
|
|
{
|
|
|
|
parameters.Add("accent", ConfigData.Accent.Value);
|
|
|
|
}
|
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
// Pages handling
|
|
|
|
if (page > 1 && !string.IsNullOrWhiteSpace(term))
|
|
|
|
{
|
|
|
|
parameters.Add("page", page.ToString());
|
|
|
|
}
|
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong category param)
|
|
|
|
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
|
|
|
|
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Info("\nXthor - Builded query for \"" + term + "\"... " + url);
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// Return our search url
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Get Torrents Page from Tracker by Query Provided
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="request">URL created by Query Builder</param>
|
|
|
|
/// <returns>Results from query</returns>
|
2021-05-03 00:38:36 +00:00
|
|
|
private async Task<string> QueryTrackerAsync(string request)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
// Cache mode not enabled or cached file didn't exist for our query
|
2021-05-03 00:38:36 +00:00
|
|
|
logger.Debug("\nQuerying tracker for results....");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// Build WebRequest for index
|
2020-10-19 21:19:10 +00:00
|
|
|
var myIndexRequest = new WebRequest
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
Type = RequestType.GET,
|
|
|
|
Url = request,
|
2021-05-03 00:38:36 +00:00
|
|
|
Encoding = Encoding
|
2017-04-15 08:45:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Request our first page
|
2020-06-11 15:09:27 +00:00
|
|
|
var results = await webclient.GetResultAsync(myIndexRequest);
|
2017-12-01 07:02:52 +00:00
|
|
|
if (results.Status == HttpStatusCode.InternalServerError) // See issue #2110
|
2020-06-09 17:36:57 +00:00
|
|
|
throw new Exception("Internal Server Error (" + results.ContentString + "), probably you reached the API limits, please reduce the number of queries");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// Return results from tracker
|
2020-06-09 17:36:57 +00:00
|
|
|
return results.ContentString;
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Check API's state
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="state">State of API</param>
|
|
|
|
private void CheckApiState(XthorError state)
|
|
|
|
{
|
|
|
|
// Switch on state
|
2021-05-03 00:38:36 +00:00
|
|
|
switch (state.Code)
|
2017-04-15 08:45:10 +00:00
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
// Everything OK
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Info("\nXthor - API State : Everything OK ... -> " + state.Descr);
|
2017-04-15 08:45:10 +00:00
|
|
|
break;
|
2017-10-29 06:50:47 +00:00
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
case 1:
|
|
|
|
// Passkey not found
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Error("\nXthor - API State : Error, Passkey not found in tracker's database, aborting... -> " + state.Descr);
|
2017-08-20 12:06:37 +00:00
|
|
|
throw new Exception("Passkey not found in tracker's database");
|
2017-04-15 08:45:10 +00:00
|
|
|
case 2:
|
|
|
|
// No results
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Info("\nXthor - API State : No results for query ... -> " + state.Descr);
|
2017-04-15 08:45:10 +00:00
|
|
|
break;
|
2017-10-29 06:50:47 +00:00
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
case 3:
|
|
|
|
// Power Saver
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Warn("\nXthor - API State : Power Saver mode, only cached query with no parameters available ... -> " + state.Descr);
|
2017-04-15 08:45:10 +00:00
|
|
|
break;
|
2017-10-29 06:50:47 +00:00
|
|
|
|
2017-04-15 08:45:10 +00:00
|
|
|
case 4:
|
|
|
|
// DDOS Attack, API disabled
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Error("\nXthor - API State : Tracker is under DDOS attack, API disabled, aborting ... -> " + state.Descr);
|
2017-08-20 12:06:37 +00:00
|
|
|
throw new Exception("Tracker is under DDOS attack, API disabled");
|
2021-05-03 00:38:36 +00:00
|
|
|
case 8:
|
|
|
|
// AntiSpam Protection
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Warn("\nXthor - API State : Triggered AntiSpam Protection -> " + state.Descr);
|
2021-05-03 00:38:36 +00:00
|
|
|
throw new Exception("Triggered AntiSpam Protection, please delay your requests !");
|
2017-04-15 08:45:10 +00:00
|
|
|
default:
|
|
|
|
// Unknown state
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Error("\nXthor - API State : Unknown state, aborting querying ... -> " + state.Descr);
|
2017-08-20 12:06:37 +00:00
|
|
|
throw new Exception("Unknown state, aborting querying");
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Validate Config entered by user on Jackett
|
|
|
|
/// </summary>
|
|
|
|
private void ValidateConfig()
|
|
|
|
{
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Debug("\nXthor - Validating Settings ... \n");
|
2017-04-15 08:45:10 +00:00
|
|
|
|
|
|
|
// Check Passkey Setting
|
|
|
|
if (string.IsNullOrEmpty(ConfigData.PassKey.Value))
|
|
|
|
{
|
|
|
|
throw new ExceptionWithConfigData("You must provide your passkey for this tracker to be allowed to use API !", ConfigData);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Debug("Xthor - Validated Setting -- PassKey (auth) => " + ConfigData.PassKey.Value);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
|
2019-09-22 18:41:34 +00:00
|
|
|
if (!string.IsNullOrEmpty(ConfigData.Accent.Value) && !string.Equals(ConfigData.Accent.Value, "1") && !string.Equals(ConfigData.Accent.Value, "2"))
|
|
|
|
{
|
|
|
|
throw new ExceptionWithConfigData("Only '1' or '2' are available in the Accent parameter.", ConfigData);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-04 16:38:37 +00:00
|
|
|
logger.Debug("Xthor - Validated Setting -- Accent (audio) => " + ConfigData.Accent.Value);
|
2017-04-15 08:45:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-28 18:53:59 +00:00
|
|
|
}
|