diff --git a/README.md b/README.md index c7d680265..3bd66075e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ We were previously focused on TV but are working on extending searches to allow * [BeyondHD](https://beyondhd.me/) * [BIT-HDTV](https://www.bit-hdtv.com) * [BitMeTV](http://www.bitmetv.org/) + * [BlueTigers](https://www.bluetigers.ca/) * [BTN](http://broadcasthe.net) * [Demonoid](http://www.demonoid.pw/) * [EuTorrents](https://eutorrents.to/) diff --git a/src/Jackett/Content/logos/bluetigers.png b/src/Jackett/Content/logos/bluetigers.png new file mode 100644 index 000000000..60b131479 Binary files /dev/null and b/src/Jackett/Content/logos/bluetigers.png differ diff --git a/src/Jackett/Indexers/BlueTigers.cs b/src/Jackett/Indexers/BlueTigers.cs new file mode 100644 index 000000000..543781ea8 --- /dev/null +++ b/src/Jackett/Indexers/BlueTigers.cs @@ -0,0 +1,211 @@ +using CsQuery; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Jackett.Models.IndexerConfig.Bespoke; + +namespace Jackett.Indexers +{ + public class BlueTigers : BaseIndexer, IIndexer + { + private string LoginUrl => SiteLink + "account-login.php"; + private string TorrentSearchUrl => SiteLink + "torrents-search.php"; + private string IndexUrl => SiteLink + "index.php"; + + private ConfigurationDataBlueTigers ConfigData + { + get { return (ConfigurationDataBlueTigers)configData; } + set { base.configData = value; } + } + + public BlueTigers(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps) + : base(name: "BlueTigers", + description: "BlueTigers - No Ratio - Private", + link: "https://www.bluetigers.ca/", + caps: new TorznabCapabilities(), + manager: i, + client: wc, + logger: l, + p: ps, + configData: new ConfigurationDataBlueTigers(@"BlueTigers can search for one or all languages. + If you select 2 languages below, results will contain all 3 languages. +
For best results change the torrents per page setting to 50 in your BlueTigers profile.")) + { + AddCategoryMapping(19, TorznabCatType.TV); + AddCategoryMapping(2, TorznabCatType.TV); + AddCategoryMapping(17, TorznabCatType.TV); + + AddCategoryMapping(52, TorznabCatType.ConsoleXbox); + + AddCategoryMapping(29, TorznabCatType.PCMac); + } + + public async Task ApplyConfiguration(JToken configJson) + { + ConfigData.LoadValuesFromJson(configJson); + + if (ConfigData.French.Value == false && ConfigData.English.Value == false && ConfigData.Spanish.Value == false) + throw new ExceptionWithConfigData("Please select at least one language.", ConfigData); + + await RequestStringWithCookies(LoginUrl, string.Empty); + var pairs = new Dictionary { + { "username", ConfigData.Username.Value }, + { "password", ConfigData.Password.Value }, + { "take_login", "1" } + }; + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, IndexUrl, SiteLink); + Regex rgx = new Regex(@"uid=[0-9]{1,10}; pass=[a-z0-9]{1,40};"); + await ConfigureIfOK(result.Cookies, rgx.IsMatch(result.Cookies), () => + { + var errorMessage = "Error while trying to login."; + throw new ExceptionWithConfigData(errorMessage, ConfigData); + }); + + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + List releases = new List(); + + NameValueCollection qParams = new NameValueCollection(); + if (ConfigData.French.Value && !ConfigData.English.Value && !ConfigData.Spanish.Value) + { + qParams.Add("lang", "1"); + } + else + { + if (!ConfigData.French.Value && ConfigData.English.Value && !ConfigData.Spanish.Value) + { + qParams.Add("lang", "2"); + } + else + { + if (!ConfigData.French.Value && !ConfigData.English.Value && ConfigData.Spanish.Value) + { + qParams.Add("lang", "3"); + } + else + { + qParams.Add("lang", "0"); + } + } + + } + + List catList = MapTorznabCapsToTrackers(query); + foreach (string cat in catList) + { + qParams.Add("cat", cat); + } + + if (!string.IsNullOrEmpty(query.SanitizedSearchTerm)) + { + qParams.Add("search", query.GetQueryString()); + } + + string queryStr = qParams.GetQueryString(); + string searchUrl = $"{TorrentSearchUrl}?incldead=0&freeleech=0&sort=id&order=ascdesc&{queryStr}"; + + List torrentRowList = new List(); + + var results = await RequestStringWithCookiesAndRetry(searchUrl); + try + { + CQ fDom = results.Content; + var firstPageRows = fDom["table[class='ttable_headinner'] > tbody > tr:not(:First-child)"]; + torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq())); + + //If a search term is used, follow upto the first 4 pages (initial plus 3 more) + if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && fDom["a[class='boutons']"].Filter("a[href*=&page=]").Length > 0) + { + int pageLinkCount; + int.TryParse(fDom["a[class='boutons']"].Filter("a[href*=&page=]").Last().Attr("href").Split(new[] { "&page=" }, StringSplitOptions.None).LastOrDefault(), out pageLinkCount); + for (int i = 1; i < Math.Min(4, pageLinkCount + 1); i++) + { + var sResults = await RequestStringWithCookiesAndRetry($"{searchUrl}&page={i}"); + CQ sDom = sResults.Content; + var additionalPageRows = sDom["table[class='ttable_headinner'] > tbody > tr:not(:First-child)"]; + torrentRowList.AddRange(additionalPageRows.Select(sRow => sRow.Cq())); + } + } + + foreach (CQ tRow in torrentRowList) + { + long torrentId = 0; + string idTarget = "bookmarks.php?torrent="; + string id = tRow.Find("a[href*=" + idTarget + "]").First().Attr("href").Trim(); + if (!string.IsNullOrEmpty(id) && id.Contains(idTarget)) + { + long.TryParse(id.Substring(id.LastIndexOf(idTarget, StringComparison.Ordinal) + idTarget.Length), out torrentId); + } + + if (torrentId <= 0) continue; + + long category = 0; + string catTarget = "torrents.php?cat="; + string cat = tRow.Find("a[href*=" + catTarget + "]").First().Attr("href").Trim(); + if (!string.IsNullOrEmpty(cat) && cat.Contains(catTarget)) + { + long.TryParse(cat.Substring(cat.LastIndexOf(catTarget, StringComparison.Ordinal) + catTarget.Length), out category); + } + + Uri guid = new Uri($"{SiteLink}torrents-details.php?hit=1&id={torrentId}"); + Uri link = new Uri($"{SiteLink}download.php?hit=1&id={torrentId}"); + Uri comments = new Uri($"{SiteLink}comments.php?type=torrent&id={torrentId}"); + string title = tRow.Find("a[href*=torrents-details.php?id=]").First().Text().Trim(); + string stats = tRow.Find("div[id=kt" + torrentId.ToString() + "]").First().Text(); + string sizeStr = new Regex("Taille:(.*)Vitesse:").Match(stats).Groups[1].ToString().Trim(); + string pubDateStr = new Regex("Ajout.:(.*)Compl.t.s").Match(stats).Groups[1].ToString().Trim(); + DateTime pubDate = DateTime.ParseExact(pubDateStr, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal); + + string statistics = tRow.Find("a[href*=torrents-details.php?id=]").First().RenderSelection().Trim(); + string startTag = " + @@ -203,6 +204,7 @@ + @@ -439,6 +441,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataBlueTigers.cs b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataBlueTigers.cs new file mode 100644 index 000000000..05cb8a51f --- /dev/null +++ b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataBlueTigers.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Jackett.Models.IndexerConfig.Bespoke +{ + public class ConfigurationDataBlueTigers : ConfigurationData + { + public StringItem Username { get; private set; } + public StringItem Password { get; private set; } + public DisplayItem Instructions { get; set; } + public BoolItem French { get; set; } + public BoolItem English { get; set; } + public BoolItem Spanish { get; set; } + + public ConfigurationDataBlueTigers(string displayInstructions) + { + Username = new StringItem { Name = "Username", Value = "" }; + Password = new StringItem { Name = "Password", Value = "" }; + Instructions = new DisplayItem(displayInstructions) { Name = "" }; + French = new BoolItem { Name = "French", Value = true }; + English = new BoolItem { Name = "English", Value = true }; + Spanish = new BoolItem { Name = "Spanish", Value = true }; + } + + public ConfigurationDataBlueTigers(JToken json) + { + ConfigurationDataNCore configData = new ConfigurationDataNCore(); + + dynamic configArray = JsonConvert.DeserializeObject(json.ToString()); + foreach (var config in configArray) + { + string propertyName = UppercaseFirst((string)config.id); + switch (propertyName) + { + case "Username": + Username = new StringItem { Name = propertyName, Value = config.value }; + break; + case "Password": + Password = new StringItem { Name = propertyName, Value = config.value }; + break; + case "French": + French = new BoolItem { Name = propertyName, Value = config.value }; + break; + case "English": + English = new BoolItem { Name = propertyName, Value = config.value }; + break; + case "Spanish": + Spanish = new BoolItem { Name = propertyName, Value = config.value }; + break; + default: + break; + } + } + } + + static string UppercaseFirst(string s) + { + if (string.IsNullOrEmpty(s)) + return string.Empty; + return char.ToUpper(s[0]) + s.Substring(1); + } + } +} \ No newline at end of file