using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using AngleSharp.Html.Parser; using Jackett.Common.Models; using Jackett.Common.Models.IndexerConfig; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; using Newtonsoft.Json.Linq; using NLog; namespace Jackett.Common.Indexers { [ExcludeFromCodeCoverage] public class RevolutionTT : IndexerBase { public override string Id => "revolutiontt"; public override string Name => "RevolutionTT"; public override string Description => "The Revolution has begun"; public override string SiteLink { get; protected set; } = "https://revolutiontt.me/"; public override Encoding Encoding => Encoding.GetEncoding("iso-8859-1"); public override string Language => "en-US"; public override string Type => "private"; public override TorznabCapabilities TorznabCaps => SetCapabilities(); private string LandingPageURL => SiteLink + "login.php"; private string LoginUrl => SiteLink + "takelogin.php"; private string SearchUrl => SiteLink + "browse.php"; private new ConfigurationDataBasicLogin configData => (ConfigurationDataBasicLogin)base.configData; public RevolutionTT(IIndexerConfigurationService configService, Utils.Clients.WebClient wc, Logger l, IProtectionService ps, ICacheService cs) : base(configService: configService, client: wc, logger: l, p: ps, cacheService: cs, configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to 100 in your Profile.")) { } private TorznabCapabilities SetCapabilities() { var caps = new TorznabCapabilities { TvSearchParams = new List { TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep }, MovieSearchParams = new List { MovieSearchParam.Q, MovieSearchParam.ImdbId }, MusicSearchParams = new List { MusicSearchParam.Q }, BookSearchParams = new List { BookSearchParam.Q } }; caps.Categories.AddCategoryMapping("23", TorznabCatType.TVAnime); caps.Categories.AddCategoryMapping("22", TorznabCatType.PC0day); caps.Categories.AddCategoryMapping("1", TorznabCatType.PCISO); caps.Categories.AddCategoryMapping("36", TorznabCatType.Books); caps.Categories.AddCategoryMapping("36", TorznabCatType.BooksEBook); caps.Categories.AddCategoryMapping("4", TorznabCatType.PCGames); caps.Categories.AddCategoryMapping("21", TorznabCatType.PCGames); caps.Categories.AddCategoryMapping("16", TorznabCatType.ConsolePS3); caps.Categories.AddCategoryMapping("40", TorznabCatType.ConsoleWii); caps.Categories.AddCategoryMapping("39", TorznabCatType.ConsoleXBox360); caps.Categories.AddCategoryMapping("35", TorznabCatType.ConsoleNDS); caps.Categories.AddCategoryMapping("34", TorznabCatType.ConsolePSP); caps.Categories.AddCategoryMapping("2", TorznabCatType.PCMac); caps.Categories.AddCategoryMapping("10", TorznabCatType.MoviesBluRay); caps.Categories.AddCategoryMapping("20", TorznabCatType.MoviesDVD); caps.Categories.AddCategoryMapping("12", TorznabCatType.MoviesHD); caps.Categories.AddCategoryMapping("44", TorznabCatType.MoviesOther); caps.Categories.AddCategoryMapping("11", TorznabCatType.MoviesSD); caps.Categories.AddCategoryMapping("19", TorznabCatType.MoviesSD); caps.Categories.AddCategoryMapping("6", TorznabCatType.Audio); caps.Categories.AddCategoryMapping("8", TorznabCatType.AudioLossless); caps.Categories.AddCategoryMapping("46", TorznabCatType.AudioOther); caps.Categories.AddCategoryMapping("29", TorznabCatType.AudioVideo); caps.Categories.AddCategoryMapping("43", TorznabCatType.TVOther); caps.Categories.AddCategoryMapping("42", TorznabCatType.TVHD); caps.Categories.AddCategoryMapping("45", TorznabCatType.TVOther); caps.Categories.AddCategoryMapping("41", TorznabCatType.TVSD); caps.Categories.AddCategoryMapping("7", TorznabCatType.TVSD); caps.Categories.AddCategoryMapping("9", TorznabCatType.XXX); caps.Categories.AddCategoryMapping("49", TorznabCatType.XXX); caps.Categories.AddCategoryMapping("47", TorznabCatType.XXXDVD); caps.Categories.AddCategoryMapping("48", TorznabCatType.XXX); return caps; } public override async Task ApplyConfiguration(JToken configJson) { LoadValuesFromJson(configJson); var pairs = new Dictionary { { "username", configData.Username.Value }, { "password", configData.Password.Value } }; // need to do an initial request to get PHP session cookie (any better way to do this?) var homePageLoad = await RequestLoginAndFollowRedirect(LandingPageURL, new Dictionary(), null, true, null, SiteLink); var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, homePageLoad.Cookies, true, null, LandingPageURL); await ConfigureIfOK(result.Cookies, result.ContentString?.Contains("/logout.php") == true, () => throw new ExceptionWithConfigData("Login failed! Check the username and password. If they are ok, try logging on the website.", configData)); return IndexerConfigurationStatus.Completed; } protected override async Task> PerformQuery(TorznabQuery query) { var releases = new List(); var qc = new NameValueCollection { {"incldead", "1"} }; if (query.IsImdbQuery) { qc.Add("titleonly", "0"); qc.Add("search", query.ImdbID); } else { qc.Add("titleonly", "1"); qc.Add("search", query.GetQueryString()); } var cats = MapTorznabCapsToTrackers(query); if (cats.Count > 0) foreach (var cat in cats) qc.Add($"c{cat}", "1"); var searchUrl = SearchUrl + "?" + qc.GetQueryString(); var results = await RequestWithCookiesAndRetryAsync(searchUrl); if (results.IsRedirect) // re-login { await ApplyConfiguration(null); results = await RequestWithCookiesAndRetryAsync(searchUrl); } try { var parser = new HtmlParser(); using var dom = parser.ParseDocument(results.ContentString); var rows = dom.QuerySelectorAll("#torrents-table > tbody > tr"); foreach (var row in rows.Skip(1)) { var qDetails = row.QuerySelector(".br_right > a"); var details = new Uri(SiteLink + qDetails.GetAttribute("href")); var title = qDetails.QuerySelector("b").TextContent; // Remove auto-generated [REQ] tag from fulfilled requests if (title.StartsWith("[REQ] ")) { title = title.Substring(6); } var qLink = row.QuerySelector("td:nth-child(4) > a"); if (qLink == null) continue; // support/donation banner var link = new Uri(SiteLink + qLink.GetAttribute("href")); // dateString format "yyyy-MMM-dd hh:mm:ss" => eg "2015-04-25 23:38:12" var dateString = row.QuerySelector("td:nth-child(6) nobr").TextContent.Trim(); var publishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture); var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(7)").InnerHtml.Split('<').First().Trim()); var files = ParseUtil.GetLongFromString(row.QuerySelector("td:nth-child(7) > a").TextContent); var grabs = ParseUtil.GetLongFromString(row.QuerySelector("td:nth-child(8)").TextContent); var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)").TextContent); var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(10)").TextContent); var category = row.QuerySelector(".br_type > a").GetAttribute("href").Replace("browse.php?cat=", string.Empty); var qImdb = row.QuerySelector("a[href*=\"www.imdb.com/\"]"); var imdb = qImdb != null ? ParseUtil.GetImdbId(qImdb.GetAttribute("href").Split('/').Last()) : null; var release = new ReleaseInfo { Details = details, Guid = details, Title = title, Link = link, PublishDate = publishDate, Size = size, Seeders = seeders, Peers = seeders + leechers, Grabs = grabs, Files = files, Category = MapTrackerCatToNewznab(category), Imdb = imdb, MinimumRatio = 1, MinimumSeedTime = 172800, // 48 hours UploadVolumeFactor = 1, DownloadVolumeFactor = 1 }; releases.Add(release); } } catch (Exception e) { OnParseError(results.ContentString, e); } return releases; } } }