From 234707b2917860c6aa75fc4f05b61a73a4db22f6 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 1 Feb 2023 15:20:09 +0200 Subject: [PATCH] Fixed: (SpeedCD) Fix wildcard when using air date --- .../Indexers/Definitions/SpeedCD.cs | 532 +++++++++--------- 1 file changed, 270 insertions(+), 262 deletions(-) diff --git a/src/NzbDrone.Core/Indexers/Definitions/SpeedCD.cs b/src/NzbDrone.Core/Indexers/Definitions/SpeedCD.cs index 0e110f9ad..8fcdfd706 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/SpeedCD.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/SpeedCD.cs @@ -19,346 +19,354 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -namespace NzbDrone.Core.Indexers.Definitions +namespace NzbDrone.Core.Indexers.Definitions; + +public class SpeedCD : TorrentIndexerBase { - public class SpeedCD : TorrentIndexerBase + public override string Name => "SpeedCD"; + public override string[] IndexerUrls => new[] + { + "https://speed.cd/", + "https://speed.click/", + "https://speeders.me/" + }; + public override string Description => "Your home now!"; + public override string Language => "en-US"; + public override Encoding Encoding => Encoding.UTF8; + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override IndexerPrivacy Privacy => IndexerPrivacy.Private; + public override IndexerCapabilities Capabilities => SetCapabilities(); + + public SpeedCD(IIndexerHttpClient httpClient, + IEventAggregator eventAggregator, + IIndexerStatusService indexerStatusService, + IConfigService configService, + Logger logger) + : base(httpClient, eventAggregator, indexerStatusService, configService, logger) + { + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new SpeedCDRequestGenerator(Settings, Capabilities, Encoding); + } + + public override IParseIndexerResponse GetParser() { - public override string Name => "SpeedCD"; - public override string[] IndexerUrls => new[] + return new SpeedCDParser(Settings, Capabilities.Categories); + } + + protected override async Task DoLogin() + { + var requestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.TrimEnd('/')}/checkpoint/API") { - "https://speed.cd/", - "https://speed.click/", - "https://speeders.me/" + Method = HttpMethod.Post, + LogResponseContent = true, + AllowAutoRedirect = true }; - public override string Description => "Your home now!"; - public override string Language => "en-US"; - public override Encoding Encoding => Encoding.UTF8; - public override DownloadProtocol Protocol => DownloadProtocol.Torrent; - public override IndexerPrivacy Privacy => IndexerPrivacy.Private; - public override IndexerCapabilities Capabilities => SetCapabilities(); - - public SpeedCD(IIndexerHttpClient httpClient, - IEventAggregator eventAggregator, - IIndexerStatusService indexerStatusService, - IConfigService configService, - Logger logger) - : base(httpClient, eventAggregator, indexerStatusService, configService, logger) - { - } - public override IIndexerRequestGenerator GetRequestGenerator() - { - return new SpeedCDRequestGenerator { Settings = Settings, Capabilities = Capabilities, Encoding = Encoding }; - } + var loginPage = await ExecuteAuth(requestBuilder.AddFormParameter("username", Settings.Username).Build()); - public override IParseIndexerResponse GetParser() + var tokenRegex = new Regex(@"name=\\""a\\"" value=\\""([^""]+)\\"""); + var matches = tokenRegex.Match(loginPage.Content); + if (!matches.Success) { - return new SpeedCDParser(Settings, Capabilities.Categories); + throw new IndexerAuthException("Error parsing the login form"); } - protected override async Task DoLogin() + var token = matches.Groups[1].Value; + + Cookies = null; + + var requestBuilder2 = new HttpRequestBuilder($"{Settings.BaseUrl.TrimEnd('/')}/checkpoint/") { - var requestBuilder = new HttpRequestBuilder(string.Format("{0}/{1}", Settings.BaseUrl.TrimEnd('/'), "checkpoint/API")) - { - Method = HttpMethod.Post, - LogResponseContent = true, - AllowAutoRedirect = true - }; + Method = HttpMethod.Post, + LogResponseContent = true, + AllowAutoRedirect = true + }; - var loginPage = await ExecuteAuth(requestBuilder.AddFormParameter("username", Settings.Username).Build()); + var authLoginRequest = requestBuilder2 + .AddFormParameter("pwd", Settings.Password) + .AddFormParameter("a", token) + .SetCookies(loginPage.GetCookies()) + .Build(); + + var response = await ExecuteAuth(authLoginRequest); + + if (CheckIfLoginNeeded(response)) + { + var parser = new HtmlParser(); + var dom = parser.ParseDocument(response.Content); + var errorMessage = dom.QuerySelector("h5")?.TextContent.Trim(); - var tokenRegex = new Regex(@"name=\\""a\\"" value=\\""([^""]+)\\"""); - var matches = tokenRegex.Match(loginPage.Content); - if (!matches.Success) + if (response.Content.Contains("Wrong Captcha!")) { - throw new IndexerAuthException("Error parsing the login form"); + errorMessage = "Captcha required due to a failed login attempt. Login via a browser to whitelist your IP and then reconfigure Prowlarr."; } - var token = matches.Groups[1].Value; - - Cookies = null; + throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report."); + } - var requestBuilder2 = new HttpRequestBuilder(string.Format("{0}/{1}", Settings.BaseUrl.TrimEnd('/'), "checkpoint/")) - { - Method = HttpMethod.Post, - LogResponseContent = true, - AllowAutoRedirect = true - }; + var cookies = response.GetCookies(); + UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30)); - var authLoginRequest = requestBuilder2 - .AddFormParameter("pwd", Settings.Password) - .AddFormParameter("a", token) - .SetCookies(loginPage.GetCookies()) - .Build(); + _logger.Debug("Authentication succeeded."); + } - var response = await ExecuteAuth(authLoginRequest); + protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) + { + return !httpResponse.Content.Contains("/browse.php"); + } - if (CheckIfLoginNeeded(response)) + private IndexerCapabilities SetCapabilities() + { + var caps = new IndexerCapabilities + { + TvSearchParams = new List { - var parser = new HtmlParser(); - var dom = parser.ParseDocument(response.Content); - var errorMessage = dom.QuerySelector("h5")?.TextContent; - if (response.Content.Contains("Wrong Captcha!")) - { - errorMessage = "Captcha required due to a failed login attempt. Login via a browser to whitelist your IP and then reconfigure Prowlarr."; - } - - throw new IndexerAuthException(errorMessage); + TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId + }, + MovieSearchParams = new List + { + MovieSearchParam.Q, MovieSearchParam.ImdbId + }, + MusicSearchParams = new List + { + MusicSearchParam.Q + }, + BookSearchParams = new List + { + BookSearchParam.Q } + }; - var cookies = response.GetCookies(); - UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30)); + caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesOther, "Movies/XviD"); + caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.Movies, "Movies/Packs"); + caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.Movies, "Movies/Kids"); + caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.MoviesHD, "Movies/HD"); + caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Movies, "Movies/DiVERSiTY"); + caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.MoviesBluRay, "Movies/B-Ray"); + caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Movies3D, "Movies/3D"); + caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesDVD, "Movies/DVD-R"); + caps.Categories.AddCategoryMapping(56, NewznabStandardCategory.Movies, "Movies/Anime"); + caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.TVSport, "TV/Sports"); + caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.TVHD, "TV/B-Ray"); + caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.TVSD, "TV/DVD-R"); + caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.TV, "TV/Packs"); + caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.TV, "TV/Kids"); + caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.TV, "TV/DiVERSiTY"); + caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.TVHD, "TV/HD"); + caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD, "TV/Episodes"); + caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVAnime, "TV/Anime"); + caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCISO, "Games/PC ISO"); + caps.Categories.AddCategoryMapping(39, NewznabStandardCategory.ConsoleWii, "Games/Wii"); + caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.ConsolePS3, "Games/PS3"); + caps.Categories.AddCategoryMapping(35, NewznabStandardCategory.Console, "Games/Nintendo"); + caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.ConsoleXBox360, "Games/XboX360"); + caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.PCMobileOther, "Mobile"); + caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "Apps/0DAY"); + caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.PCMac, "Mac"); + caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.Books, "Educational"); + caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "Books-Mags"); + caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.Audio, "Music/Audio"); + caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Music/Flac"); + caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.Audio, "Music/Pack"); + caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioVideo, "Music/Video"); + + return caps; + } +} - _logger.Debug("SpeedCD authentication succeeded."); - } +public class SpeedCDRequestGenerator : IIndexerRequestGenerator +{ + private readonly SpeedCDSettings _settings; + private readonly IndexerCapabilities _capabilities; + private readonly Encoding _encoding; + + public SpeedCDRequestGenerator(SpeedCDSettings settings, IndexerCapabilities capabilities, Encoding encoding) + { + _settings = settings; + _capabilities = capabilities; + _encoding = encoding; + } + + private IEnumerable GetPagedRequests(string term, int[] categories, bool deep = false) + { + var parameters = new List(); - protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) + var catList = _capabilities.Categories.MapTorznabCapsToTrackers(categories); + foreach (var cat in catList) { - return !httpResponse.Content.Contains("/browse.php"); + parameters.Add(cat); } - private IndexerCapabilities SetCapabilities() + if (_settings.FreeleechOnly) { - var caps = new IndexerCapabilities - { - TvSearchParams = new List - { - TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId - }, - MovieSearchParams = new List - { - MovieSearchParam.Q, MovieSearchParam.ImdbId - }, - MusicSearchParams = new List - { - MusicSearchParam.Q - }, - BookSearchParams = new List - { - BookSearchParam.Q - } - }; - - caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesOther, "Movies/XviD"); - caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.Movies, "Movies/Packs"); - caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.Movies, "Movies/Kids"); - caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.MoviesHD, "Movies/HD"); - caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Movies, "Movies/DiVERSiTY"); - caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.MoviesBluRay, "Movies/B-Ray"); - caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Movies3D, "Movies/3D"); - caps.Categories.AddCategoryMapping(40, NewznabStandardCategory.MoviesDVD, "Movies/DVD-R"); - caps.Categories.AddCategoryMapping(56, NewznabStandardCategory.Movies, "Movies/Anime"); - caps.Categories.AddCategoryMapping(50, NewznabStandardCategory.TVSport, "TV/Sports"); - caps.Categories.AddCategoryMapping(52, NewznabStandardCategory.TVHD, "TV/B-Ray"); - caps.Categories.AddCategoryMapping(53, NewznabStandardCategory.TVSD, "TV/DVD-R"); - caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.TV, "TV/Packs"); - caps.Categories.AddCategoryMapping(55, NewznabStandardCategory.TV, "TV/Kids"); - caps.Categories.AddCategoryMapping(57, NewznabStandardCategory.TV, "TV/DiVERSiTY"); - caps.Categories.AddCategoryMapping(49, NewznabStandardCategory.TVHD, "TV/HD"); - caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD, "TV/Episodes"); - caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVAnime, "TV/Anime"); - caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.PCISO, "Games/PC ISO"); - caps.Categories.AddCategoryMapping(39, NewznabStandardCategory.ConsoleWii, "Games/Wii"); - caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.ConsolePS3, "Games/PS3"); - caps.Categories.AddCategoryMapping(35, NewznabStandardCategory.Console, "Games/Nintendo"); - caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.ConsoleXBox360, "Games/XboX360"); - caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.PCMobileOther, "Mobile"); - caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.PC0day, "Apps/0DAY"); - caps.Categories.AddCategoryMapping(51, NewznabStandardCategory.PCMac, "Mac"); - caps.Categories.AddCategoryMapping(54, NewznabStandardCategory.Books, "Educational"); - caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Books, "Books-Mags"); - caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.Audio, "Music/Audio"); - caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Audio, "Music/Flac"); - caps.Categories.AddCategoryMapping(44, NewznabStandardCategory.Audio, "Music/Pack"); - caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.AudioVideo, "Music/Video"); - - return caps; + parameters.Add("freeleech"); } - } - public class SpeedCDRequestGenerator : IIndexerRequestGenerator - { - public SpeedCDSettings Settings { get; set; } - public IndexerCapabilities Capabilities { get; set; } - public Encoding Encoding { get; set; } - - private IEnumerable GetPagedRequests(string term, int[] categories, bool deep = false) + if (_settings.ExcludeArchives) { - var searchUrl = $"{Settings.BaseUrl.TrimEnd('/')}/browse/"; - - var qc = new List(); + parameters.Add("norar"); + } - var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories); - foreach (var cat in catList) - { - qc.Add(cat); - } + if (deep) + { + parameters.Add("deep"); + } - if (Settings.FreeleechOnly) - { - qc.Add("freeleech"); - } + parameters.Add("q"); + parameters.Add(term.UrlEncode(_encoding)); - if (deep) - { - qc.Add("deep"); - } + var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/browse/{string.Join("/", parameters)}"; - qc.Add("q"); - qc.Add(term.UrlEncode(Encoding)); + var request = new IndexerRequest(searchUrl, HttpAccept.Html); - searchUrl += string.Join("/", qc); + yield return request; + } - var request = new IndexerRequest(searchUrl, HttpAccept.Html); + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); - yield return request; - } + var term = $"{searchCriteria.SanitizedSearchTerm}"; - public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()) { - var pageableRequests = new IndexerPageableRequestChain(); + term = $"{searchCriteria.FullImdbId}"; + } - var term = $"{searchCriteria.SanitizedSearchTerm}"; + pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())); - if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()) - { - term = $"{searchCriteria.FullImdbId}"; - } + return pageableRequests; + } - pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())); + public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); - return pageableRequests; - } + pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); - public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); + return pageableRequests; + } - pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); + public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); - return pageableRequests; - } + var term = $"{searchCriteria.SanitizedTvSearchString}"; - public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) + if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()) { - var pageableRequests = new IndexerPageableRequestChain(); + term = $"{searchCriteria.FullImdbId}"; - var term = $"{searchCriteria.SanitizedTvSearchString}"; - - if (searchCriteria.FullImdbId.IsNotNullOrWhiteSpace()) + if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace()) { - term = $"{searchCriteria.FullImdbId}"; + term += $" {searchCriteria.EpisodeSearchString}"; - if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace()) - { - term += $" {searchCriteria.EpisodeSearchString}"; - } - - if (searchCriteria.Season.HasValue) + if (searchCriteria.Season.HasValue && searchCriteria.Episode.IsNullOrWhiteSpace()) { term += "*"; } } - - pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())); - - return pageableRequests; } - public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); - - pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); + pageableRequests.Add(GetPagedRequests(term.Trim(), searchCriteria.Categories, searchCriteria.FullImdbId.IsNotNullOrWhiteSpace())); - return pageableRequests; - } - - public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria) - { - var pageableRequests = new IndexerPageableRequestChain(); + return pageableRequests; + } - pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); + public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); - return pageableRequests; - } + pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); - public Func> GetCookies { get; set; } - public Action, DateTime?> CookiesUpdater { get; set; } + return pageableRequests; } - public class SpeedCDParser : IParseIndexerResponse + public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria) { - private readonly SpeedCDSettings _settings; - private readonly IndexerCapabilitiesCategories _categories; + var pageableRequests = new IndexerPageableRequestChain(); - public SpeedCDParser(SpeedCDSettings settings, IndexerCapabilitiesCategories categories) - { - _settings = settings; - _categories = categories; - } + pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); - public IList ParseResponse(IndexerResponse indexerResponse) - { - var torrentInfos = new List(); - - var parser = new HtmlParser(); - var dom = parser.ParseDocument(indexerResponse.Content); - var rows = dom.QuerySelectorAll("div.boxContent > table > tbody > tr"); - - foreach (var row in rows) - { - var title = CleanTitle(row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]").TextContent); - var downloadUrl = new Uri(_settings.BaseUrl + row.QuerySelector("td:nth-child(4) a[href^=\"/download/\"]").GetAttribute("href").TrimStart('/')); - var infoUrl = new Uri(_settings.BaseUrl + row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]").GetAttribute("href").TrimStart('/')); - var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(6)").TextContent); - var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent); - var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent); - var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)").TextContent); + return pageableRequests; + } - var pubDateStr = row.QuerySelector("td:nth-child(2) span[class^=\"elapsedDate\"]").GetAttribute("title").Replace(" at", ""); - var publishDate = DateTime.ParseExact(pubDateStr, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture); + public Func> GetCookies { get; set; } + public Action, DateTime?> CookiesUpdater { get; set; } +} - var cat = row.QuerySelector("td:nth-child(1) a").GetAttribute("href").Split('/').Last(); - var downloadVolumeFactor = row.QuerySelector("td:nth-child(2) span:contains(\"[Freeleech]\")") != null ? 0 : 1; +public class SpeedCDParser : IParseIndexerResponse +{ + private readonly SpeedCDSettings _settings; + private readonly IndexerCapabilitiesCategories _categories; - var release = new TorrentInfo - { - Title = title, - DownloadUrl = downloadUrl.AbsoluteUri, - Guid = infoUrl.AbsoluteUri, - InfoUrl = infoUrl.AbsoluteUri, - PublishDate = publishDate, - Categories = _categories.MapTrackerCatToNewznab(cat), - Size = size, - Grabs = grabs, - Seeders = seeders, - Peers = seeders + leechers, - MinimumRatio = 1, - MinimumSeedTime = 259200, // 72 hours - DownloadVolumeFactor = downloadVolumeFactor, - UploadVolumeFactor = 1 - }; - - torrentInfos.Add(release); - } + public SpeedCDParser(SpeedCDSettings settings, IndexerCapabilitiesCategories categories) + { + _settings = settings; + _categories = categories; + } - return torrentInfos.ToArray(); - } + public IList ParseResponse(IndexerResponse indexerResponse) + { + var releaseInfos = new List(); - public Action, DateTime?> CookiesUpdater { get; set; } + var parser = new HtmlParser(); + var dom = parser.ParseDocument(indexerResponse.Content); - private static string CleanTitle(string title) + var rows = dom.QuerySelectorAll("div.boxContent > table > tbody > tr"); + foreach (var row in rows) { - title = Regex.Replace(title, @"\[REQ(UEST)?\]", string.Empty, RegexOptions.Compiled | RegexOptions.IgnoreCase); + var title = CleanTitle(row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]")?.TextContent); + var downloadUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(4) a[href^=\"/download/\"]")?.GetAttribute("href")?.TrimStart('/'); + var infoUrl = _settings.BaseUrl + row.QuerySelector("td:nth-child(2) > div > a[href^=\"/t/\"]")?.GetAttribute("href")?.TrimStart('/'); + var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)")?.TextContent); + var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(9)")?.TextContent); + var dateAdded = row.QuerySelector("td:nth-child(2) span[class^=\"elapsedDate\"]")?.GetAttribute("title")?.Replace(" at", ""); + var cat = row.QuerySelector("td:nth-child(1) a")?.GetAttribute("href")?.Split('/').Last(); + var downloadVolumeFactor = row.QuerySelector("td:nth-child(2) span:contains(\"[Freeleech]\")") != null ? 0 : 1; + + var release = new TorrentInfo + { + Guid = infoUrl, + InfoUrl = infoUrl, + DownloadUrl = downloadUrl, + Title = title, + Categories = _categories.MapTrackerCatToNewznab(cat), + PublishDate = DateTime.ParseExact(dateAdded, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal), + Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(6)")?.TextContent), + Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)")?.TextContent), + Seeders = seeders, + Peers = seeders + leechers, + DownloadVolumeFactor = downloadVolumeFactor, + UploadVolumeFactor = 1, + MinimumRatio = 1, + MinimumSeedTime = 259200 // 72 hours + }; - return title.Trim(' ', '.'); + releaseInfos.Add(release); } + + return releaseInfos.ToArray(); } - public class SpeedCDSettings : UserPassTorrentBaseSettings + private static string CleanTitle(string title) { - [FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")] - public bool FreeleechOnly { get; set; } + title = Regex.Replace(title, @"\[REQ(UEST)?\]", string.Empty, RegexOptions.Compiled | RegexOptions.IgnoreCase); + + return title.Trim(' ', '.'); } + + public Action, DateTime?> CookiesUpdater { get; set; } +} + +public class SpeedCDSettings : UserPassTorrentBaseSettings +{ + [FieldDefinition(4, Label = "Freeleech Only", Type = FieldType.Checkbox, HelpText = "Search freeleech torrents only")] + public bool FreeleechOnly { get; set; } + + [FieldDefinition(5, Label = "Exclude Archives", Type = FieldType.Checkbox, HelpText = "Exclude torrents containing RAR files from results")] + public bool ExcludeArchives { get; set; } }