From ab55c6af7c4af73761cbc5fef7c883e925008d15 Mon Sep 17 00:00:00 2001 From: Diego Heras Date: Mon, 6 Jan 2020 18:32:40 +0100 Subject: [PATCH] adds HD-Olimpo, private Spanish tracker. resolves #5142 (#6828) --- README.md | 1 + src/Jackett.Common/Indexers/HDOlimpo.cs | 222 ++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 src/Jackett.Common/Indexers/HDOlimpo.cs diff --git a/README.md b/README.md index 33fcd86f5..71497bd49 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * Greek Team * HacheDe * HD-Forever (HDF) + * HD-Olimpo * HD-Only (HDO) * HD-Space (HDS) * HD-Spain diff --git a/src/Jackett.Common/Indexers/HDOlimpo.cs b/src/Jackett.Common/Indexers/HDOlimpo.cs new file mode 100644 index 000000000..d68913f9c --- /dev/null +++ b/src/Jackett.Common/Indexers/HDOlimpo.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using CsQuery; +using Jackett.Common.Models; +using Jackett.Common.Models.IndexerConfig; +using Jackett.Common.Services.Interfaces; +using Jackett.Common.Utils.Clients; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using static Jackett.Common.Models.IndexerConfig.ConfigurationData; + +namespace Jackett.Common.Indexers +{ + public class HDOlimpo : BaseWebIndexer + { + private string LoginUrl => SiteLink + "login"; + private string LogoutUrl => SiteLink + "logout"; + private string SearchUrl => SiteLink + "torrents/buscar?page=1"; + private string CommentsUrl => SiteLink + "torrents/detalles/"; + private string DownloadUrl => SiteLink + "torrents/descargar/"; + + private new ConfigurationDataBasicLoginWithEmail configData + { + get { return (ConfigurationDataBasicLoginWithEmail)base.configData; } + set { base.configData = value; } + } + + public HDOlimpo(IIndexerConfigurationService configService, WebClient w, Logger l, IProtectionService ps) + : base(name: "HD-Olimpo", + description: "HD-Olimpo is a SPANISH site for HD content", + link: "https://hdolimpo.co/", + caps: new TorznabCapabilities(), + configService: configService, + client: w, + logger: l, + p: ps, + configData: new ConfigurationDataBasicLoginWithEmail()) + { + Encoding = Encoding.UTF8; + Language = "es-es"; + Type = "private"; + + var premiumItem = new BoolItem() { Name = "Include Premium torrents in search results", Value = false }; + configData.AddDynamic("IncludePremium", premiumItem); + + // TODO: add subcategories + AddCategoryMapping(1, TorznabCatType.Movies, "Movies"); + AddCategoryMapping(2, TorznabCatType.TV, "TV"); + AddCategoryMapping(3, TorznabCatType.TVDocumentary, "Documentary"); + AddCategoryMapping(4, TorznabCatType.TVAnime, "Anime"); + AddCategoryMapping(8, TorznabCatType.TVSport, "Sports"); + AddCategoryMapping(9, TorznabCatType.XXX, "XXX"); + AddCategoryMapping(10, TorznabCatType.Audio, "Music"); + } + + public override async Task ApplyConfiguration(JToken configJson) + { + LoadValuesFromJson(configJson); + + await DoLogin(); + + return IndexerConfigurationStatus.RequiresTesting; + } + + private async Task DoLogin() + { + var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty); + var token = new Regex("name=\"_token\" value=\"(.*?)\">").Match(loginPage.Content).Groups[1].ToString(); + var pairs = new Dictionary { + { "_token", token }, + { "email", configData.Email.Value }, + { "password", configData.Password.Value }, + { "remember", "on" } + }; + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains(LogoutUrl), () => + { + CQ dom = result.Content; + var messageEl = dom[".error"]; + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, configData); + }); + } + + protected override async Task> PerformQuery(TorznabQuery query) + { + var includePremium = ((BoolItem)configData.GetDynamic("IncludePremium")).Value; + + var pairs = new Dictionary(); + pairs.Add("freetorrent", "false"); + pairs.Add("ordenar_por", "created_at"); + pairs.Add("orden", "desc"); + pairs.Add("titulo", query.GetQueryString()); + + var cats = MapTorznabCapsToTrackers(query); + var category = cats.Count == 1 ? cats.First() : "0"; + pairs.Add("categoria", category); + + var boundary = "---------------------------" + (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds.ToString().Replace(".", ""); + var bodyParts = new List(); + + foreach (var pair in pairs) + { + var part = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"" + pair.Key + "\"\r\n" + + "\r\n" + + pair.Value; + bodyParts.Add(part); + } + + bodyParts.Add("--" + boundary + "--"); + var body = string.Join("\r\n", bodyParts); + + var headers = new Dictionary(); + headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary); + addXsrfTokenHeader(headers, configData.CookieHeader.Value); + + var response = await PostDataWithCookies(SearchUrl, pairs, configData.CookieHeader.Value, SiteLink, headers, body); + if (response.Content.StartsWith("")) + { + //Cookie appears to expire after a period of time or logging in to the site via browser + await DoLogin(); + + addXsrfTokenHeader(headers, configData.CookieHeader.Value); + response = await PostDataWithCookies(SearchUrl, pairs, configData.CookieHeader.Value, SiteLink, headers, body); + } + + List releases = ParseResponse(query, response, includePremium); + + return releases; + } + + private void addXsrfTokenHeader(Dictionary headers, string cookie) + { + var xsrfToken = new Regex("XSRF-TOKEN=([^;]+)").Match(cookie).Groups[1].ToString(); + xsrfToken = Uri.UnescapeDataString(xsrfToken); + headers["X-XSRF-TOKEN"] = xsrfToken; + } + + private List ParseResponse(TorznabQuery query, WebClientStringResult response, bool includePremium) + { + List releases = new List(); + + var torrents = CheckResponse(response); + + try + { + foreach (var torrent in torrents) + { + var release = new ReleaseInfo(); + + release.Title = (string)torrent["titulo"] + " " + (string)torrent["titulo_extra"]; + + // for downloading "premium" torrents you need special account + if ((string) torrent["premium"] == "si") + { + if (includePremium) + release.Title += " [PREMIUM]"; + else + continue; + } + + release.Comments = new Uri(CommentsUrl + (string)torrent["id"]); + release.Guid = release.Comments; + + release.PublishDate = DateTime.Now; + if (torrent["created_at"] != null) + release.PublishDate = DateTime.Parse((string)torrent["created_at"]); + + release.Category = MapTrackerCatToNewznab((string)torrent["categoria"]); + release.Size = (long)torrent["size"]; + + release.Seeders = (int)torrent["seeders"]; + release.Peers = release.Seeders + (int)torrent["leechers"]; + release.Grabs = (long)torrent["snatched"]; + + release.InfoHash = (string)torrent["plain_info_hash"]; + release.Link = new Uri(DownloadUrl + (string) torrent["id"]); + + var files = (JArray)JsonConvert.DeserializeObject((string)torrent["files_list"]); + release.Files = files.Count; + + release.DownloadVolumeFactor = (string)torrent["freetorrent"] == "0" ? 1 : 0; + release.UploadVolumeFactor = (string)torrent["doubletorrent"] == "0" ? 1 : 2; + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(response.Content, ex); + } + + return releases; + } + + private JArray CheckResponse(WebClientStringResult response) + { + try + { + var json = JsonConvert.DeserializeObject(response.Content); + if (!(json is JObject) || json["torrents"] == null || !(json["torrents"]["data"] is JArray) || json["torrents"]["data"] == null) + throw new Exception("Server error"); + return (JArray)json["torrents"]["data"]; + } + catch (Exception e) + { + logger.Error("CheckResponse() Error: ", e.Message); + throw new Exception(response.Content); + } + } + } +}