/// <summary> /// Execute our search query /// </summary> /// <param name="query">Query</param> /// <returns>Releases</returns> protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchTerm = query.GetEpisodeSearchString() + " " + query.SanitizedSearchTerm; // use episode search string first, see issue #1202 searchTerm = searchTerm.Trim(); searchTerm = searchTerm.ToLower(); if (EnhancedAnime && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110))) { var regex = new Regex(" ([0-9]+)"); searchTerm = regex.Replace(searchTerm, " E$1"); } // Check cache first so we don't query the server (if search term used or not in dev mode) if (!DevMode && !string.IsNullOrEmpty(searchTerm)) { lock (cache) { // Remove old cache items CleanCache(); // Search in cache var cachedResult = cache.FirstOrDefault(i => i.Query == searchTerm); if (cachedResult != null) { return(cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray()); } } } // Build our query var request = BuildQuery(searchTerm, query, ApiEndpoint); // Getting results & Store content var results = await QueryExec(request); try { // Deserialize our Json Response var xthorResponse = JsonConvert.DeserializeObject <XthorResponse>(results); // Check Tracker's State CheckApiState(xthorResponse.error); // If contains torrents if (xthorResponse.torrents != null) { // Adding each torrent row to releases releases.AddRange(xthorResponse.torrents.Select(torrent => { //issue #3847 replace multi keyword if (!string.IsNullOrEmpty(ReplaceMulti)) { var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])"); torrent.name = regex.Replace(torrent.name, "$1" + ReplaceMulti + "$2"); } // issue #8759 replace vostfr and subfrench with English if (ConfigData.Vostfr.Value) { torrent.name = torrent.name.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH"); } var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.added); //TODO replace with download link? var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.id.ToString())); var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.id.ToString())); var link = new Uri(torrent.download_link); var release = new ReleaseInfo { // Mapping data Category = MapTrackerCatToNewznab(torrent.category.ToString()), Title = torrent.name, Seeders = torrent.seeders, Peers = torrent.seeders + torrent.leechers, MinimumRatio = 1, MinimumSeedTime = 345600, PublishDate = publishDate, Size = torrent.size, Grabs = torrent.times_completed, Files = torrent.numfiles, UploadVolumeFactor = 1, DownloadVolumeFactor = (torrent.freeleech == 1 ? 0 : 1), Guid = guid, Details = details, Link = link, TMDb = torrent.tmdb_id }; //TODO make consistent with other trackers if (DevMode) { Output(release.ToString()); } return(release); })); } } catch (Exception ex) { OnParseError("Unable to parse result \n" + ex.StackTrace, ex); } // Return found releases return(releases); }
/// <summary> /// Execute our search query /// </summary> /// <param name="query">Query</param> /// <returns>Releases</returns> protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchTerm = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); searchTerm = searchTerm.Trim(); searchTerm = searchTerm.ToLower(); searchTerm = searchTerm.Replace(" ", "."); if (EnhancedAnimeSearch && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110))) { var regex = new Regex(" ([0-9]+)"); searchTerm = regex.Replace(searchTerm, " E$1"); } // Multiple page support var nextPage = 1; var followingPages = true; do { // Build our query var request = BuildQuery(searchTerm, query, ApiEndpoint, nextPage); // Getting results logger.Info("\nXthor - Querying API page " + nextPage); var results = await QueryTrackerAsync(request); // Torrents Result Count var torrentsCount = 0; try { // Deserialize our Json Response var xthorResponse = JsonConvert.DeserializeObject <XthorResponse>(results); // Check Tracker's State CheckApiState(xthorResponse.Error); // If contains torrents if (xthorResponse.Torrents != null) { // Store torrents rows count result torrentsCount = xthorResponse.Torrents.Count(); logger.Info("\nXthor - Found " + torrentsCount + " torrents on current page."); // Adding each torrent row to releases // Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407 releases.AddRange(xthorResponse.Torrents .Where(torrent => torrent.Category != 106).Select(torrent => { //issue #3847 replace multi keyword if (!string.IsNullOrEmpty(MultiReplacement)) { var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])"); torrent.Name = regex.Replace(torrent.Name, "$1" + MultiReplacement + "$2"); } // issue #8759 replace vostfr and subfrench with English if (SubReplacement) { torrent.Name = torrent.Name.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH"); } var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.Added); //TODO replace with download link? var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); var link = new Uri(torrent.Download_link); var release = new ReleaseInfo { // Mapping data Category = MapTrackerCatToNewznab(torrent.Category.ToString()), Title = torrent.Name, Seeders = torrent.Seeders, Peers = torrent.Seeders + torrent.Leechers, MinimumRatio = 1, MinimumSeedTime = 345600, PublishDate = publishDate, Size = torrent.Size, Grabs = torrent.Times_completed, Files = torrent.Numfiles, UploadVolumeFactor = 1, DownloadVolumeFactor = (torrent.Freeleech == 1 ? 0 : 1), Guid = guid, Details = details, Link = link, TMDb = torrent.Tmdb_id }; return(release); })); nextPage++; } else { logger.Info("\nXthor - No results found on page " + nextPage + ", stopping follow of next page."); // No results or no more results available followingPages = false; break; } } catch (Exception ex) { OnParseError("Unable to parse result \n" + ex.StackTrace, ex); } // Stop ? if (query.IsTmdbQuery && MaxPagesBypassForTMDB) { if (nextPage > MaxPagesHardLimit) { logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page hard limit reached."); break; } logger.Info("\nXthor - Continue to next page " + nextPage + " due to TMDB request and activated max page bypass for this type of query. Max page hard limit: 4."); continue; } else { if (torrentsCount < 32) { logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due max available results reached."); break; } else if (nextPage > MaxPages) { logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page limit reached."); break; } else if (query.IsTest) { logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to index test query."); break; } } } while (followingPages); // Check if there is duplicate and return unique rows - Xthor API can be very buggy ! var uniqReleases = releases.GroupBy(x => x.Guid).Select(x => x.First()).ToList(); var errorPercentage = 1 - ((double)uniqReleases.Count() / releases.Count()); if (errorPercentage >= 0.25) { logger.Warn("\nXthor - High percentage error detected: " + string.Format("{0:0.0%}", errorPercentage) + "\nWe strongly recommend that you lower max page to 1, as there is no benefit to grab additionnals.\nTracker API sent us duplicated pages with same results, even if we deduplicate returned rows, please consider to lower as it's unnecessary and increase time used for query for the same result."); } // Return found releases return(uniqReleases); }
/// <summary> /// Execute our search query /// </summary> /// <param name="query">Query</param> /// <returns>Releases</returns> protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchTerm = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); if (EnhancedAnimeSearch && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110))) { var regex = new Regex(" ([0-9]+)"); searchTerm = regex.Replace(searchTerm, " E$1"); } searchTerm = searchTerm.Trim(); searchTerm = searchTerm.ToLower(); searchTerm = searchTerm.Replace(" ", "."); // Multiple page support var nextPage = 1; var followingPages = true; do { // Build our query var request = BuildQuery(searchTerm, query, SearchUrl, nextPage); // Getting results logger.Info("\nAbnormal - Querying API page " + nextPage); var dom = new HtmlParser().ParseDocument(await QueryExecAsync(request)); var results = dom.QuerySelectorAll(".table-rows > tbody > tr:not(.mvc-grid-empty-row)"); // Torrents Result Count var torrentsCount = results.Length; try { // If contains torrents if (torrentsCount > 0) { logger.Info("\nAbnormal - Found " + torrentsCount + " torrents on current page."); // Adding each torrent row to releases releases.AddRange(results.Select(torrent => { // Selectors var id = torrent.QuerySelector("td.grid-release-column > a").GetAttribute("href"); // ID var name = torrent.QuerySelector("td.grid-release-column > a").TextContent; // Release Name var categoryId = torrent.QuerySelector("td.grid-cat-column > a").GetAttribute("href"); // Category var completed = torrent.QuerySelector("td:nth-of-type(3)").TextContent; // Completed var seeders = torrent.QuerySelector("td.text-green").TextContent; // Seeders var leechers = torrent.QuerySelector("td.text-red").TextContent; // Leechers var size = torrent.QuerySelector("td:nth-of-type(5)").TextContent; // Size var release = new ReleaseInfo { // Mapping data Category = MapTrackerCatToNewznab(Regex.Match(categoryId, @"\d+").Value), Title = name, Seeders = int.Parse(Regex.Match(seeders, @"\d+").Value), Peers = int.Parse(Regex.Match(seeders, @"\d+").Value) + int.Parse(Regex.Match(leechers, @"\d+").Value), Grabs = int.Parse(Regex.Match(completed, @"\d+").Value) + int.Parse(Regex.Match(leechers, @"\d+").Value), MinimumRatio = 1, MinimumSeedTime = 172800, Size = ReleaseInfo.GetBytes(size.Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb")), UploadVolumeFactor = 1, DownloadVolumeFactor = 1, PublishDate = DateTime.Now, Guid = new Uri(TorrentDetailsUrl.Replace("{id}", Regex.Match(id, @"\d+").Value)), Details = new Uri(TorrentDetailsUrl.Replace("{id}", Regex.Match(id, @"\d+").Value)), Link = new Uri(TorrentDownloadUrl.Replace("{id}", Regex.Match(id, @"\d+").Value)) }; // Multi Replacement if (!string.IsNullOrEmpty(MultiReplacement)) { var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])"); release.Title = regex.Replace(release.Title, "$1" + MultiReplacement + "$2"); } // Sub Replacement if (SubReplacement) { release.Title = release.Title.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH"); } // Freeleech if (torrent.QuerySelector("img[alt=\"Freeleech\"]") != null) { release.DownloadVolumeFactor = 0; } return(release); })); if (torrentsCount == 50) { // Is there more pages to follow ? var morePages = dom.QuerySelectorAll("div.mvc-grid-pager > button").Last().GetAttribute("tabindex"); if (morePages == "-1") { followingPages = false; } } nextPage++; } else { logger.Info("\nAbnormal - No results found on page " + nextPage + ", stopping follow of next page."); // No results or no more results available followingPages = false; break; } } catch (Exception ex) { OnParseError("Unable to parse result \n" + ex.StackTrace, ex); } // Stop ? if (torrentsCount < int.Parse(dom.QuerySelector(".mvc-grid-pager-rows").GetAttribute("value"))) { logger.Info("\nAbnormal - Stopping follow of next page " + nextPage + " due max available results reached."); break; } else if (nextPage > MaxPages) { logger.Info("\nAbnormal - Stopping follow of next page " + nextPage + " due to page limit reached."); break; } else if (query.IsTest) { logger.Info("\nAbnormal - Stopping follow of next page " + nextPage + " due to index test query."); break; } } while (followingPages); // Return found releases return(releases); }
/// <summary> /// Execute our search query /// </summary> /// <param name="query">Query</param> /// <returns>Releases</returns> protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchTerm = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString(); searchTerm = searchTerm.Trim(); searchTerm = searchTerm.ToLower(); searchTerm = searchTerm.Replace(" ", "."); if (EnhancedAnime && query.HasSpecifiedCategories && (query.Categories.Contains(TorznabCatType.TVAnime.ID) || query.Categories.Contains(100032) || query.Categories.Contains(100101) || query.Categories.Contains(100110))) { var regex = new Regex(" ([0-9]+)"); searchTerm = regex.Replace(searchTerm, " E$1"); } logger.Info("\nXthor - Search requested for \"" + searchTerm + "\""); // Multiple page support var nextPage = 1; var followingPages = true; do { // Build our query var request = BuildQuery(searchTerm, query, ApiEndpoint, nextPage); // Getting results logger.Info("\nXthor - Querying API page " + nextPage); var results = await QueryTrackerAsync(request); // Torrents Result Count var torrentsCount = 0; try { // Deserialize our Json Response var xthorResponse = JsonConvert.DeserializeObject <XthorResponse>(results); // Check Tracker's State CheckApiState(xthorResponse.Error); // If contains torrents if (xthorResponse.Torrents != null) { // Store torrents rows count result torrentsCount = xthorResponse.Torrents.Count(); logger.Info("\nXthor - Found " + torrentsCount + " torrents on current page."); // Adding each torrent row to releases // Exclude hidden torrents (category 106, example => search 'yoda' in the API) #10407 releases.AddRange(xthorResponse.Torrents .Where(torrent => torrent.Category != 106).Select(torrent => { //issue #3847 replace multi keyword if (!string.IsNullOrEmpty(ReplaceMulti)) { var regex = new Regex("(?i)([\\.\\- ])MULTI([\\.\\- ])"); torrent.Name = regex.Replace(torrent.Name, "$1" + ReplaceMulti + "$2"); } // issue #8759 replace vostfr and subfrench with English if (ConfigData.Vostfr.Value) { torrent.Name = torrent.Name.Replace("VOSTFR", "ENGLISH").Replace("SUBFRENCH", "ENGLISH"); } var publishDate = DateTimeUtil.UnixTimestampToDateTime(torrent.Added); //TODO replace with download link? var guid = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); var details = new Uri(TorrentDetailsUrl.Replace("{id}", torrent.Id.ToString())); var link = new Uri(torrent.Download_link); var release = new ReleaseInfo { // Mapping data Category = MapTrackerCatToNewznab(torrent.Category.ToString()), Title = torrent.Name, Seeders = torrent.Seeders, Peers = torrent.Seeders + torrent.Leechers, MinimumRatio = 1, MinimumSeedTime = 345600, PublishDate = publishDate, Size = torrent.Size, Grabs = torrent.Times_completed, Files = torrent.Numfiles, UploadVolumeFactor = 1, DownloadVolumeFactor = (torrent.Freeleech == 1 ? 0 : 1), Guid = guid, Details = details, Link = link, TMDb = torrent.Tmdb_id }; return(release); })); nextPage++; } else { logger.Info("\nXthor - No results found on page " + (nextPage - 1) + ", stopping follow of next page."); // No results or no more results available followingPages = false; } } catch (Exception ex) { OnParseError("Unable to parse result \n" + ex.StackTrace, ex); } // Stop ? if (nextPage > MaxPageLoads | torrentsCount < 32 | string.IsNullOrWhiteSpace(searchTerm)) { logger.Info("\nXthor - Stopping follow of next page " + nextPage + " due to page limit or max available results reached or indexer test."); followingPages = false; } } while (followingPages); // Check if there is duplicate and return unique rows - Xthor API can be very buggy ! var uniqReleases = releases.GroupBy(x => x.Guid).Select(x => x.First()).ToList(); // Return found releases return(uniqReleases); }