From e9ae2d4cd8b590748ed41bbcc8ee78aa6bd42860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Robert?= <sebastien.robert@intech.lu> Date: Tue, 29 Dec 2015 11:17:18 +0100 Subject: [PATCH] WiHD Provider --- src/Jackett/Content/logos/wihd.png | Bin 0 -> 24917 bytes src/Jackett/Indexers/WiHD.cs | 702 ++++++++++++++++++ src/Jackett/Jackett.csproj | 5 +- .../Bespoke/ConfigurationDataWiHD.cs | 55 ++ 4 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 src/Jackett/Content/logos/wihd.png create mode 100644 src/Jackett/Indexers/WiHD.cs create mode 100644 src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs diff --git a/src/Jackett/Content/logos/wihd.png b/src/Jackett/Content/logos/wihd.png new file mode 100644 index 0000000000000000000000000000000000000000..c6ccd81f4a9b7ed9ff2423ee4d55e00d622ae174 GIT binary patch literal 24917 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 MUHx3vIVCg!0KoVHi2wiq literal 0 HcmV?d00001 diff --git a/src/Jackett/Indexers/WiHD.cs b/src/Jackett/Indexers/WiHD.cs new file mode 100644 index 00000000..09a61f17 --- /dev/null +++ b/src/Jackett/Indexers/WiHD.cs @@ -0,0 +1,702 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using CsQuery; +using Jackett.Models; +using Jackett.Models.IndexerConfig.Bespoke; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; + +namespace Jackett.Indexers +{ + /// <summary> + /// Provider for WiHD Private French Tracker + /// </summary> + public class WiHD : BaseIndexer, IIndexer + { + private string LoginUrl { get { return SiteLink + "login"; } } + private string LoginCheckUrl { get { return SiteLink + "login_check"; } } + private string SearchUrl { get { return SiteLink + "torrent/ajaxfiltertorrent/"; } } + private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>(); + private CQ fDom = null; + private bool Latency { get { return ConfigData.Latency.Value; } } + private bool DevMode { get { return ConfigData.DevMode.Value; } } + + private ConfigurationDataWiHD ConfigData + { + get { return (ConfigurationDataWiHD)configData; } + set { base.configData = value; } + } + + public WiHD(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps) + : base( + name: "WiHD", + description: "Your World in High Definition", + link: "http://world-in-hd.net/", + caps: new TorznabCapabilities(), + manager: i, + client: w, + logger: l, + p: ps, + downloadBase: "http://world-in-hd.net/torrents/download/", + configData: new ConfigurationDataWiHD()) + { + // Clean capabilities + TorznabCaps.Categories.Clear(); + + // Movies + AddCategoryMapping("565af82b1fd35761568b4572", TorznabCatType.MoviesHD); // 1080P + AddCategoryMapping("565af82b1fd35761568b4574", TorznabCatType.MoviesHD); // 720P + AddCategoryMapping("565af82b1fd35761568b4576", TorznabCatType.MoviesHD); // HDTV + AddCategoryMapping("565af82b1fd35761568b4578", TorznabCatType.MoviesBluRay); // Bluray + AddCategoryMapping("565af82b1fd35761568b457a", TorznabCatType.MoviesBluRay); // Bluray Remux + AddCategoryMapping("565af82b1fd35761568b457c", TorznabCatType.Movies3D); // Bluray 3D + + // TV + AddCategoryMapping("565af82d1fd35761568b4587", TorznabCatType.TVHD); // 1080P + AddCategoryMapping("565af82d1fd35761568b4589", TorznabCatType.TVHD); // 720P + AddCategoryMapping("565af82d1fd35761568b458b", TorznabCatType.TVHD); // HDTV + AddCategoryMapping("565af82d1fd35761568b458d", TorznabCatType.TVHD); // Bluray + AddCategoryMapping("565af82d1fd35761568b458f", TorznabCatType.TVHD); // Bluray Remux + AddCategoryMapping("565af82d1fd35761568b4591", TorznabCatType.TVHD); // Bluray 3D + + // Anime + AddCategoryMapping("565af82d1fd35761568b459c", TorznabCatType.TVAnime); // 1080P + AddCategoryMapping("565af82d1fd35761568b459e", TorznabCatType.TVAnime); // 720P + AddCategoryMapping("565af82d1fd35761568b45a0", TorznabCatType.TVAnime); // HDTV + AddCategoryMapping("565af82d1fd35761568b45a2", TorznabCatType.TVAnime); // Bluray + AddCategoryMapping("565af82d1fd35761568b45a4", TorznabCatType.TVAnime); // Bluray Remux + AddCategoryMapping("565af82d1fd35761568b45a6", TorznabCatType.TVAnime); // Bluray 3D + + // Other + AddCategoryMapping("565af82d1fd35761568b45af", TorznabCatType.PC); // Apps + AddCategoryMapping("565af82d1fd35761568b45b1", TorznabCatType.AudioVideo); // Clips + AddCategoryMapping("565af82d1fd35761568b45b3", TorznabCatType.AudioOther); // Audios Tracks of Movies/TV/Anime + AddCategoryMapping("565af82d1fd35761568b45b5", TorznabCatType.TVDocumentary); // Documentary + AddCategoryMapping("565af82d1fd35761568b45b7", TorznabCatType.MoviesBluRay); // Bluray (ALL) + } + + /// <summary> + /// Configure our WiHD Provider + /// </summary> + /// <param name="configJson">Our params in Json</param> + /// <returns>Configuration state</returns> + public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) + { + // Retrieve config values set by Jackett's user + ConfigData.LoadValuesFromJson(configJson); + + // Check & Validate Config + validateConfig(); + + // Setting our data for a better emulated browser (maximum security) + // TODO: Encoded Content not supported by Jackett at this time + // emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate"); + + // If we want to simulate a browser + if (ConfigData.Browser.Value) { + + // Clean headers + emulatedBrowserHeaders.Clear(); + + // Inject headers + emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value); + emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value); + emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString()); + emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString()); + emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value); + } + + + // Getting login form to retrieve CSRF token + var myRequest = new Utils.Clients.WebRequest() + { + Url = LoginUrl + }; + + // Add our headers to request + myRequest.Headers = emulatedBrowserHeaders; + + // Get login page + var loginPage = await webclient.GetString(myRequest); + + // Retrieving our CSRF token + CQ loginPageDom = loginPage.Content; + var csrfToken = loginPageDom["input[name=\"_csrf_token\"]"].Last(); + + // Building login form data + var pairs = new Dictionary<string, string> { + { "_csrf_token", csrfToken.Attr("value") }, + { "_username", ConfigData.Username.Value }, + { "_password", ConfigData.Password.Value }, + { "_remember_me", "on" }, + { "_submit", "" } + }; + + // Do the login + var request = new Utils.Clients.WebRequest(){ + Cookies = loginPage.Cookies, + PostData = pairs, + Referer = LoginUrl, + Type = RequestType.POST, + Url = LoginUrl, + Headers = emulatedBrowserHeaders + }; + + // Perform loggin + latencyNow(); + output("Perform loggin.. with " + LoginCheckUrl); + var response = await RequestLoginAndFollowRedirect(LoginCheckUrl, pairs, loginPage.Cookies, true, null, null); + + // Test if we are logged in + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout"), () => { + // Oops, unable to login + throw new ExceptionWithConfigData("Failed to login", configData); + }); + + return IndexerConfigurationStatus.RequiresTesting; + } + + /// <summary> + /// Execute our search query + /// </summary> + /// <param name="query">Query</param> + /// <returns>Releases</returns> + public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query) + { + var releases = new List<ReleaseInfo>(); + var torrentRowList = new List<CQ>(); + var searchTerm = query.GetQueryString(); + var searchUrl = SearchUrl; + + // Check cache first so we don't query the server + if(!DevMode) + { + lock (cache) + { + // Remove old cache items + CleanCache(); + + var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault(); + if (cachedResult != null) + return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray(); + } + } + + // Add emulated XHR request + emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest"); + + // Request our first page + latencyNow(); + var results = await RequestStringWithCookiesAndRetry(buildQuery(searchTerm, query, searchUrl), null, null, emulatedBrowserHeaders); + fDom = results.Content; + + try + { + // Find number of results + int nbResults = ParseUtil.CoerceInt(Regex.Match(fDom["div.ajaxtotaltorrentcount"].Text(), @"\d+").Value); + output("\nFound " + nbResults + " results for query !"); + + // Find torrent rows + var firstPageRows = findTorrentRows(); + output("There are " + firstPageRows.Length + " results on the first page !"); + torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq())); + + // Calculate numbers of pages available for this search query + int pageLinkCount = (int)Math.Ceiling((double)nbResults / firstPageRows.Length); // Based on number results and number of torrents on first page + output("--> Pages available for query: " + pageLinkCount); + + // If we have a term used for search and pagination result superior to one + if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1) + { + // Starting with page #2 + for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++) + { + output("Processing page #" + i); + + // Request our page + latencyNow(); + results = await RequestStringWithCookiesAndRetry(buildQuery(searchTerm, query, searchUrl, i), null, null, emulatedBrowserHeaders); + + // Assign response + fDom = results.Content; + + // Process page results + var additionalPageRows = findTorrentRows(); + torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq())); + } + } + + // Loop on results + foreach (CQ tRow in torrentRowList) + { + output("\n=>> Torrent #" + (releases.Count + 1)); + + // Release Name + string name = tRow.Find(".torrent-h3 > h3 > a").Attr("title").ToString(); + output("Release: " + name); + + // Category + string categoryID = tRow.Find(".category > img").Attr("src").Split('/').Last().ToString(); + string categoryName = tRow.Find(".category > img").Attr("title").ToString(); + output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)) + " (" + categoryName + ")"); + + // Uploader + string uploader = tRow.Find(".uploader > span > a").Attr("title").ToString(); + output("Uploader: " + uploader); + + // Seeders + int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".seeders")[0].LastChild.ToString(), @"\d+").Value); + output("Seeders: " + seeders); + + // Leechers + int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".leechers")[0].LastChild.ToString(), @"\d+").Value); + output("Leechers: " + leechers); + + // Completed + int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".completed")[0].LastChild.ToString(), @"\d+").Value); + output("Completed: " + completed); + + // Comments + int comments = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".comments")[0].LastChild.ToString(), @"\d+").Value); + output("Comments: " + comments); + + // Size & Publish Date + string infosData = tRow.Find(".torrent-h3 > span")[0].LastChild.ToString().Trim(); + IList<string> infosList = infosData.Split('-').Select(s => s.Trim()).Where(s => s != String.Empty).ToList(); + + // --> Size + var size = ReleaseInfo.GetBytes(infosList[1].Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb")); + output("Size: " + infosList[1] + " (" + size + " bytes)"); + + // --> Publish Date + IList<string> clockList = infosList[0].Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList(); + var clock = agoToDate(clockList); + output("Released on: " + clock.ToString()); + + // Torrent Details URL + string details = tRow.Find(".torrent-h3 > h3 > a").Attr("href").ToString().TrimStart('/'); + Uri detailsLink = new Uri(SiteLink + details); + output("Details: " + detailsLink.AbsoluteUri); + + // Torrent Comments URL + Uri commentsLink = new Uri(SiteLink + details + "#tab_2"); + output("Comments: " + commentsLink.AbsoluteUri); + + // Torrent Download URL + string download = tRow.Find(".download-item > a").Attr("href").ToString().TrimStart('/'); + Uri downloadLink = new Uri(SiteLink + download); + output("Download: " + downloadLink.AbsoluteUri); + + // Building release infos + var release = new ReleaseInfo(); + release.Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)); + release.Title = name; + release.Seeders = seeders; + release.Peers = seeders + leechers; + release.MinimumRatio = 1; + release.MinimumSeedTime = 345600; + release.PublishDate = clock; + release.Size = size; + release.Guid = detailsLink; + release.Comments = commentsLink; + release.Link = downloadLink; + releases.Add(release); + } + + } + catch (Exception ex) + { + OnParseError("Error, unable to parse result", ex); + } + + // Remove our XHR request header + emulatedBrowserHeaders.Remove("X-Requested-With"); + + // Return found releases + return releases; + } + + /// <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> + /// <param name="page">Page number to request</param> + /// <param name="exclu">Exclusive state</param> + /// <param name="freeleech">Freeleech state</param> + /// <param name="reseed">Reseed state</param> + /// <returns>URL to query for parsing and processing results</returns> + private string buildQuery(string term, TorznabQuery query, string url, int page = 1) + { + var parameters = new NameValueCollection(); + List<string> categoriesList = MapTorznabCapsToTrackers(query); + string categories = null; + + if (string.IsNullOrWhiteSpace(term)) + { + // If no search string provided, use default (for testing purposes) + term = "the walking dead"; + } + + // Encode & Add search term to URL + url += Uri.EscapeDataString(term); + + // Check if we are processing a new page + if (page > 1) + { + // Adding page number to query + url += "/" + page.ToString(); + } + + // Adding interrogation point + url += "?"; + + // Building our tracker query + parameters.Add("exclu", Convert.ToInt32(ConfigData.Exclusive.Value).ToString()); + parameters.Add("freeleech", Convert.ToInt32(ConfigData.Freeleech.Value).ToString()); + parameters.Add("reseed", Convert.ToInt32(ConfigData.Reseed.Value).ToString()); + + // Loop on Categories needed + foreach (string category in categoriesList) + { + // If last, build ! + if(categoriesList.Last() == category) + { + // Adding previous categories to URL with latest category + parameters.Add(Uri.EscapeDataString("subcat[]"), category + categories); + } + else + { + // Build categories parameter + categories += "&" + Uri.EscapeDataString("subcat[]") + "=" + category; + } + } + + // Add timestamp as a query param (for no caching) + parameters.Add("_", UnixTimeNow().ToString()); + + // Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong subcat[] param) + url += string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a])); + + output("Builded query for \"" + term + "\"... with " + url); + + // Return our search url + return url; + } + + /// <summary> + /// Generate a random fake latency to avoid detection on tracker side + /// </summary> + private void latencyNow() + { + // Need latency ? + if(Latency) + { + var random = new Random(DateTime.Now.Millisecond); + int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value)); + output("Latency Faker => Sleeping for " + waiting + " ms..."); + // Sleep now... + System.Threading.Thread.Sleep(waiting); + } + } + + /// <summary> + /// Generate an UTC Unix TimeStamp + /// </summary> + /// <returns>Unix TimeStamp</returns> + private long UnixTimeNow() + { + var timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)); + return (long)timeSpan.TotalSeconds; + } + + /// <summary> + /// Find torrent rows in search pages + /// </summary> + /// <returns>JQuery Object</returns> + private CQ findTorrentRows() + { + // Return all occurencis of torrents found + return fDom[".torrent-item"]; + } + + /// <summary> + /// Convert Ago date to DateTime + /// </summary> + /// <param name="clockList"></param> + /// <returns>A DateTime</returns> + private DateTime agoToDate(IList<string> clockList) + { + DateTime release = DateTime.Now; + foreach(var ago in clockList) + { + // Check for years + if(ago.Contains("Années") || ago.Contains("Année")) + { + // Number of years to remove + int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddYears(-years); + + continue; + } + // Check for months + else if (ago.Contains("Mois")) + { + // Number of months to remove + int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddMonths(-months); + + continue; + } + // Check for days + else if (ago.Contains("Jours") || ago.Contains("Jour")) + { + // Number of days to remove + int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddDays(-days); + + continue; + } + // Check for hours + else if (ago.Contains("Heures") || ago.Contains("Heure")) + { + // Number of hours to remove + int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddHours(-hours); + + continue; + } + // Check for minutes + else if (ago.Contains("Minutes") || ago.Contains("Minute")) + { + // Number of minutes to remove + int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddMinutes(-minutes); + + continue; + } + // Check for seconds + else if (ago.Contains("Secondes") || ago.Contains("Seconde")) + { + // Number of seconds to remove + int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value); + // Removing + release = release.AddSeconds(-seconds); + + continue; + } + else + { + output("Unable to detect release date of torrent", "error"); + //throw new Exception("Unable to detect release date of torrent"); + } + } + return release; + } + + /// <summary> + /// Retrieve category ID from media ID + /// </summary> + /// <param name="media">Media ID</param> + /// <returns>Category ID</returns> + private string mediaToCategory(string media, string name) + { + // Declare our Dictionnary -- Media ID (key) <-> Category ID (value) + Dictionary<string, string> dictionary = new Dictionary<string, string>(); + + // Movies + dictionary.Add("565af82b1fd35761568b4573", "565af82b1fd35761568b4572"); // 1080P + dictionary.Add("565af82b1fd35761568b4575", "565af82b1fd35761568b4574"); // 720P + dictionary.Add("565af82b1fd35761568b4577", "565af82b1fd35761568b4576"); // HDTV + dictionary.Add("565af82b1fd35761568b4579", "565af82b1fd35761568b4578"); // Bluray + dictionary.Add("565af82b1fd35761568b457b", "565af82b1fd35761568b457a"); // Bluray Remux + dictionary.Add("565af82b1fd35761568b457d", "565af82b1fd35761568b457c"); // Bluray 3D + + // TV + dictionary.Add("565af82d1fd35761568b4588", "565af82d1fd35761568b4587"); // 1080P + dictionary.Add("565af82d1fd35761568b458a", "565af82d1fd35761568b4589"); // 720P + dictionary.Add("565af82d1fd35761568b458c", "565af82d1fd35761568b458b"); // HDTV + dictionary.Add("565af82d1fd35761568b458e", "565af82d1fd35761568b458d"); // Bluray + dictionary.Add("565af82d1fd35761568b4590", "565af82d1fd35761568b458f"); // Bluray Remux + dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b4591"); // Bluray 3D + + // Anime + dictionary.Add("565af82d1fd35761568b459d", "565af82d1fd35761568b459c"); // 1080P + dictionary.Add("565af82d1fd35761568b459f", "565af82d1fd35761568b459e"); // 720P + dictionary.Add("565af82d1fd35761568b45a1", "565af82d1fd35761568b45a0"); // HDTV + dictionary.Add("565af82d1fd35761568b45a3", "565af82d1fd35761568b45a2"); // Bluray + dictionary.Add("565af82d1fd35761568b45a5", "565af82d1fd35761568b45a4"); // Bluray Remux + // BUG ~~ Media ID for Anime BR 3D is same as TV BR 3D ~~ + //dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b45a6"); // Bluray 3D + + // Other + dictionary.Add("565af82d1fd35761568b45b0", "565af82d1fd35761568b45af"); // Apps + dictionary.Add("565af82d1fd35761568b45b2", "565af82d1fd35761568b45b1"); // Clips + dictionary.Add("565af82d1fd35761568b45b4", "565af82d1fd35761568b45b3"); // Audios Tracks of Movies/TV/Anime + dictionary.Add("565af82d1fd35761568b45b6", "565af82d1fd35761568b45b5"); // Documentary + dictionary.Add("565af82d1fd35761568b45b8", "565af82d1fd35761568b45b7"); // Bluray (ALL) + + // Check if we know this media ID + if (dictionary.ContainsKey(media)) + { + // Due to a bug on tracker side, check for a specific id/name as image is same for TV/Anime BR 3D + if(media == "565af82d1fd35761568b4592" && name == "Animations - Bluray 3D") + { + // If it's an Anime BR 3D + return "565af82d1fd35761568b45a6"; + } + else + { + // Return category ID for media ID + return dictionary[media]; + } + } + else + { + // Media ID unknown + throw new Exception("Media ID Unknow !"); + } + } + + /// <summary> + /// Output message for logging or developpment (console) + /// </summary> + /// <param name="message">Message to output</param> + /// <param name="level">Level for Logger</param> + private void output(string message, string level = "debug") + { + // Check if we are in dev mode + if(DevMode) + { + // Output message to console + Console.WriteLine(message); + } + else + { + // Send message to logger with level + switch (level) + { + default: + goto case "debug"; + case "debug": + logger.Debug(message); + break; + case "info": + logger.Info(message); + break; + case "error": + logger.Error(message); + break; + } + } + } + + /// <summary> + /// Validate Config entered by user on Jackett + /// </summary> + private void validateConfig() + { + // Check Username Setting + if (string.IsNullOrEmpty(ConfigData.Username.Value)) + { + throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData); + } + + // Check Password Setting + if (string.IsNullOrEmpty(ConfigData.Password.Value)) + { + throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData); + } + + // Check Max Page Setting + if (!string.IsNullOrEmpty(ConfigData.Pages.Value)) + { + try + { + output("Settings -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value)); + } + catch (Exception) + { + throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData); + } + } + else + { + throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData); + } + + // Check Latency Setting + if (ConfigData.Latency.Value) + { + // Check Latency Start Setting + if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value)) + { + try + { + output("Settings -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value)); + } + catch (Exception) + { + throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData); + } + } + else + { + throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData); + } + + // Check Latency End Setting + if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value)) + { + try + { + output("Settings -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value)); + } + catch (Exception) + { + throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData); + } + } + else + { + throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData); + } + } + + // Check Browser Setting + if (ConfigData.Browser.Value) + { + // Check ACCEPT header Setting + if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value)) + { + throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData); + } + + // Check ACCEPT-LANG header Setting + if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value)) + { + throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData); + } + + // Check USER-AGENT header Setting + if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value)) + { + throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData); + } + } + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 628b554d..b90f7b63 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -207,10 +207,12 @@ <Compile Include="Indexers\ImmortalSeed.cs" /> <Compile Include="Indexers\FileList.cs" /> <Compile Include="Indexers\Abstract\AvistazTracker.cs" /> + <Compile Include="Indexers\WiHD.cs" /> <Compile Include="Indexers\XSpeeds.cs" /> <Compile Include="Models\GitHub\Asset.cs" /> <Compile Include="Models\GitHub\Release.cs" /> <Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" /> + <Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataWiHD.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilter.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataAPIKey.cs" /> <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSSAndDisplay.cs" /> @@ -562,6 +564,7 @@ <Content Include="Content\logos\tvchaosuk.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="Content\logos\wihd.png" /> <Content Include="Content\logos\xspeeds.png" /> <Content Include="Content\setup_indexer.html"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -626,4 +629,4 @@ </PropertyGroup> <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> </Target> -</Project> +</Project> \ No newline at end of file diff --git a/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs new file mode 100644 index 00000000..95075e2f --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs @@ -0,0 +1,55 @@ +namespace Jackett.Models.IndexerConfig.Bespoke +{ + class ConfigurationDataWiHD : ConfigurationData + { + public DisplayItem CredentialsWarning { get; private set; } + public StringItem Username { get; private set; } + public StringItem Password { get; private set; } + public DisplayItem PagesWarning { get; private set; } + public StringItem Pages { get; private set; } + public BoolItem Exclusive { get; private set; } + public BoolItem Freeleech { get; private set; } + public BoolItem Reseed { get; private set; } + public DisplayItem SecurityWarning { get; private set; } + public BoolItem Latency { get; private set; } + public BoolItem Browser { get; private set; } + public DisplayItem LatencyWarning { get; private set; } + public StringItem LatencyStart { get; private set; } + public StringItem LatencyEnd { get; private set; } + public DisplayItem HeadersWarning { get; private set; } + public StringItem HeaderAccept { get; private set; } + public StringItem HeaderAcceptLang { get; private set; } + public BoolItem HeaderDNT { get; private set; } + public BoolItem HeaderUpgradeInsecure { get; private set; } + public StringItem HeaderUserAgent { get; private set; } + public DisplayItem DevWarning { get; private set; } + public BoolItem DevMode { get; private set; } + + public ConfigurationDataWiHD() + : base() + { + CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" }; + Username = new StringItem { Name = "Username (Required)", Value = "" }; + Password = new StringItem { Name = "Password (Required)", Value = "" }; + PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name = "Preferences" }; + Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" }; + Exclusive = new BoolItem() { Name = "Exclusive Only (Optional)", Value = false }; + Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false }; + Reseed = new BoolItem() { Name = "Reseed Needed Only (Optional)", Value = false }; + SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" }; + Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = true }; + Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true }; + LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" }; + LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" }; + LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" }; + HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" }; + HeaderAccept = new StringItem { Name = "Accept", Value = "" }; + HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" }; + HeaderDNT = new BoolItem { Name = "DNT", Value = false }; + HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false }; + HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" }; + DevWarning = new DisplayItem("<b>Devlopement Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling devlopement mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li></ul>") { Name = "Devlopement" }; + DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false }; + } + } +} -- GitLab