public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = SearchUrl; var queryCollection = new NameValueCollection(); queryCollection.Add("total", "146"); // Not sure what this is about but its required! var cat = "0"; var queryCats = MapTorznabCapsToTrackers(query); if (queryCats.Count == 1) { cat = queryCats.First().ToString(); } queryCollection.Add("cat", cat); queryCollection.Add("searchin", "filename"); queryCollection.Add("search", searchString); queryCollection.Add("page", "1"); searchUrl += "?" + queryCollection.GetQueryString(); var extraHeaders = new Dictionary <string, string>() { { "X-Requested-With", "XMLHttpRequest" } }; var response = await RequestStringWithCookiesAndRetry(searchUrl, null, SearchUrlReferer, extraHeaders); var results = response.Content; try { CQ dom = results; var rows = dom["tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); var qRow = row.Cq(); var qTitleLink = qRow.Find("td:eq(1) a:eq(0)").First(); release.Title = qTitleLink.Find("strong").Text().Trim(); // If we search an get no results, we still get a table just with no info. if (string.IsNullOrWhiteSpace(release.Title)) { break; } release.Description = release.Title; release.Guid = new Uri(qTitleLink.Attr("href")); release.Comments = release.Guid; var dateString = qRow.Find("td:eq(4)").Text(); release.PublishDate = DateTime.ParseExact(dateString, "dd MMM yy", CultureInfo.InvariantCulture); var qLink = qRow.Find("td:eq(2) a"); release.Link = new Uri(qLink.Attr("href")); var sizeStr = qRow.Find("td:eq(5)").Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); var connections = qRow.Find("td:eq(7)").Text().Trim().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); release.Seeders = ParseUtil.CoerceInt(connections[0].Trim()); release.Peers = ParseUtil.CoerceInt(connections[1].Trim()) + release.Seeders; var rCat = row.Cq().Find("td:eq(0) a").First().Attr("href"); var rCatIdx = rCat.IndexOf("cat="); if (rCatIdx > -1) { rCat = rCat.Substring(rCatIdx + 4); } release.Category = MapTrackerCatToNewznab(rCat); releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
public static TorznabQuery ToTorznabQuery(TorznabRequest request) { var query = new TorznabQuery() { QueryType = "search", SearchTerm = request.q, ImdbID = request.imdbid, Episode = request.ep, }; if (request.t != null) { query.QueryType = request.t; } if (!string.IsNullOrWhiteSpace(request.extended)) { query.Extended = ParseUtil.CoerceInt(request.extended); } if (!string.IsNullOrWhiteSpace(request.limit)) { query.Limit = ParseUtil.CoerceInt(request.limit); } if (!string.IsNullOrWhiteSpace(request.offset)) { query.Offset = ParseUtil.CoerceInt(request.offset); } bool _cache; if (bool.TryParse(request.cache, out _cache)) { query.Cache = _cache; } if (request.cat != null) { query.Categories = request.cat.Split(',').Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => int.Parse(s)).ToArray(); } else { if (query.QueryType == "movie" && !string.IsNullOrWhiteSpace(request.imdbid)) { query.Categories = new int[] { TorznabCatType.Movies.ID } } ; else { query.Categories = new int[0]; } } if (!string.IsNullOrWhiteSpace(request.rid)) { query.RageID = int.Parse(request.rid); } if (!string.IsNullOrWhiteSpace(request.tvdbid)) { query.TvdbID = int.Parse(request.tvdbid); } if (!string.IsNullOrWhiteSpace(request.season)) { query.Season = int.Parse(request.season); } if (!string.IsNullOrWhiteSpace(request.tmdbid)) { query.TmdbID = int.Parse(request.tmdbid); } if (!string.IsNullOrWhiteSpace(request.album)) { query.Album = request.album; } if (!string.IsNullOrWhiteSpace(request.artist)) { query.Artist = request.artist; } if (!string.IsNullOrWhiteSpace(request.label)) { query.Label = request.label; } if (!string.IsNullOrWhiteSpace(request.track)) { query.Track = request.track; } if (!string.IsNullOrWhiteSpace(request.year)) { query.Year = int.Parse(request.year); } if (!string.IsNullOrWhiteSpace(request.genre)) { query.Genre = request.genre.Split(','); } if (!string.IsNullOrWhiteSpace(request.title)) { query.Title = request.title; } if (!string.IsNullOrWhiteSpace(request.author)) { query.Author = request.author; } return(query); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 3, 5, DayOfWeek.Sunday); TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 4, 0, 0), 10, 5, DayOfWeek.Sunday); TimeSpan delta = new TimeSpan(1, 0, 0); TimeZoneInfo.AdjustmentRule adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1999, 10, 1), DateTime.MaxValue.Date, delta, startTransition, endTransition); TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment }; TimeZoneInfo germanyTz = TimeZoneInfo.CreateCustomTimeZone("W. Europe Standard Time", new TimeSpan(1, 0, 0), "(GMT+01:00) W. Europe Standard Time", "W. Europe Standard Time", "W. Europe DST Time", adjustments); var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = IndexUrl; var queryCollection = new NameValueCollection(); queryCollection.Add("strWebValue", "torrent"); queryCollection.Add("strWebAction", "search"); queryCollection.Add("sort", "torrent_added"); queryCollection.Add("by", "d"); queryCollection.Add("type", "0"); queryCollection.Add("do_search", "suchen"); queryCollection.Add("time", "0"); queryCollection.Add("details", "title"); if (!string.IsNullOrWhiteSpace(searchString)) { queryCollection.Add("searchstring", searchString); } foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add("dirs" + cat, "1"); } searchUrl += "?" + queryCollection.GetQueryString(); logger.Error(searchUrl); logger.Error(CookieHeader); var response = await RequestBytesWithCookies(searchUrl); var results = Encoding.GetEncoding("iso-8859-1").GetString(response.Content); try { CQ dom = results; var rows = dom["table.torrenttable > tbody > tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); release.MinimumRatio = 0.8; release.MinimumSeedTime = 0; var qRow = row.Cq(); var qDetailsLink = qRow.Find("a[href^=index.php?strWebValue=torrent&strWebAction=details]").First(); release.Title = qDetailsLink.Text(); var qCatLink = qRow.Find("a[href^=index.php?strWebValue=torrent&strWebAction=search&dir=]").First(); var qDLLink = qRow.Find("a[href^=index.php?strWebValue=torrent&strWebAction=download&id=]").First(); var qSeeders = qRow.Find("td.column1:eq(3)"); var qLeechers = qRow.Find("td.column2:eq(3)"); var qDateStr = qRow.Find("font:has(a)").First(); var qSize = qRow.Find("td.column2[align=center]").First(); var catStr = qCatLink.Attr("href").Split('=')[3].Split('#')[0]; release.Category = MapTrackerCatToNewznab(catStr); release.Link = new Uri(SiteLink + qDLLink.Attr("href")); release.Comments = new Uri(SiteLink + qDetailsLink.Attr("href")); release.Guid = release.Link; var sizeStr = qSize.Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(qSeeders.Text()); release.Peers = ParseUtil.CoerceInt(qLeechers.Text()) + release.Seeders; var dateStr = qDateStr.Text().Trim(); var dateStrParts = dateStr.Split(); DateTime dateGerman; if (dateStrParts[0] == "Heute") { dateGerman = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateStrParts[1]); } else if (dateStrParts[0] == "Gestern") { dateGerman = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateStrParts[1]) - TimeSpan.FromDays(1); } else { dateGerman = DateTime.SpecifyKind(DateTime.ParseExact(dateStrParts[0] + dateStrParts[1], "dd.MM.yyyyHH:mm", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); } DateTime pubDateUtc = TimeZoneInfo.ConvertTimeToUtc(dateGerman, germanyTz); release.PublishDate = pubDateUtc.ToLocalTime(); var grabs = qRow.Find("td:nth-child(7)").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); if (qRow.Find("img[src=\"themes/images/freeleech.png\"]").Length >= 1) { release.DownloadVolumeFactor = 0; } else if (qRow.Find("img[src=\"themes/images/DL50.png\"]").Length >= 1) { release.DownloadVolumeFactor = 0.5; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
private async Task <List <ReleaseInfo> > ParseUserSearchAsync(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchUrl = BrowseUrl; var isSearchAnime = query.Categories.Any(s => s == TorznabCatType.TVAnime.ID); var searchTerm = FixSearchTerm(query); var queryCollection = new NameValueCollection { { "searchstr", StripSearchString(searchTerm, isSearchAnime) }, { "order_by", "time" }, { "order_way", "desc" }, { "group_results", "1" }, { "action", "basic" }, { "searchsubmit", "1" } }; foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add("filter_cat[" + cat + "]", "1"); } searchUrl += "?" + queryCollection.GetQueryString(); var results = await RequestStringWithCookies(searchUrl); if (results.IsRedirect) { // re-login await ApplyConfiguration(null); results = await RequestStringWithCookies(searchUrl); } try { const string rowsSelector = "table.torrent_table > tbody > tr:not(tr.colhead)"; var searchResultParser = new HtmlParser(); var searchResultDocument = searchResultParser.ParseDocument(results.Content); var rows = searchResultDocument.QuerySelectorAll(rowsSelector); ICollection <int> groupCategory = null; string groupTitle = null; string groupYearStr = null; var categoryStr = ""; foreach (var row in rows) { try { // ignore sub groups info row, it's just an row with an info about the next section, something like "Dual Áudio" or "Legendado" if (row.QuerySelector(".edition_info") != null) { continue; } var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]"); var title = StripSearchString(qDetailsLink.TextContent, false); var seasonEp = _EpisodeRegex.Match(qDetailsLink.TextContent).Value; ICollection <int> category = null; string yearStr = null; if (row.ClassList.Contains("group") || row.ClassList.Contains("torrent")) // group/ungrouped headers { var qCatLink = row.QuerySelector("a[href^=\"/torrents.php?filter_cat\"]"); categoryStr = qCatLink.GetAttribute("href").Split('=')[1].Split('&')[0]; category = MapTrackerCatToNewznab(categoryStr); yearStr = qDetailsLink.NextSibling.TextContent.Trim().TrimStart('[').TrimEnd(']'); if (row.ClassList.Contains("group")) // group headers { groupCategory = category; groupTitle = title; groupYearStr = yearStr; continue; } } var release = new ReleaseInfo { MinimumRatio = 1, MinimumSeedTime = 0 }; var qDlLink = row.QuerySelector("a[href^=\"torrents.php?action=download\"]"); var qSize = row.QuerySelector("td:nth-last-child(4)"); var qGrabs = row.QuerySelector("td:nth-last-child(3)"); var qSeeders = row.QuerySelector("td:nth-last-child(2)"); var qLeechers = row.QuerySelector("td:nth-last-child(1)"); var qFreeLeech = row.QuerySelector("strong[title=\"Free\"]"); if (row.ClassList.Contains("group_torrent")) // torrents belonging to a group { release.Description = Regex.Match(qDetailsLink.TextContent, @"\[.*?\]").Value; release.Title = ParseTitle(groupTitle, seasonEp, groupYearStr, categoryStr); release.Category = groupCategory; } else if (row.ClassList.Contains("torrent")) // standalone/un grouped torrents { release.Description = row.QuerySelector("div.torrent_info").TextContent; release.Title = ParseTitle(title, seasonEp, yearStr, categoryStr); release.Category = category; } release.Description = release.Description.Replace(" / Free", ""); // Remove Free Tag release.Description = release.Description.Replace("Full HD", "1080p"); // Handles HDR conflict release.Description = release.Description.Replace("/ HD /", "/ 720p /"); release.Description = release.Description.Replace("/ HD]", "/ 720p]"); release.Description = release.Description.Replace("4K", "2160p"); release.Description = release.Description.Replace("SD", "480p"); release.Description = release.Description.Replace("Dual Áudio", "Dual"); // If it ain't nacional there will be the type of the audio / original audio if (!release.Description.Contains("Nacional")) { release.Description = Regex.Replace( release.Description, @"(Dual|Legendado|Dublado) \/ (.*?) \/", "$1 /"); } // Adjust the description in order to can be read by Radarr and Sonarr var cleanDescription = release.Description.Trim().TrimStart('[').TrimEnd(']'); string[] titleElements; //Formats the title so it can be parsed later var stringSeparators = new[] { " / " }; titleElements = cleanDescription.Split(stringSeparators, StringSplitOptions.None); // release.Title += string.Join(" ", titleElements); release.Title = release.Title.Trim(); if (titleElements.Length < 6) { // Usually non movies / series could have less than 6 elements, eg: Books. release.Title += " " + string.Join(" ", titleElements); } else { release.Title += " " + titleElements[5] + " " + titleElements[3] + " " + titleElements[1] + " " + titleElements[2] + " " + titleElements[4] + " " + string.Join( " ", titleElements.Skip(6)); } // This tracker does not provide an publish date to search terms (only on last 24h page) release.PublishDate = DateTime.Today; // check for previously stripped search terms if (!query.IsImdbQuery && !query.MatchQueryStringAND(release.Title, null, searchTerm)) { continue; } var size = qSize.TextContent; release.Size = ReleaseInfo.GetBytes(size); release.Link = new Uri(SiteLink + qDlLink.GetAttribute("href")); release.Comments = new Uri(SiteLink + qDetailsLink.GetAttribute("href")); release.Guid = release.Link; release.Grabs = ParseUtil.CoerceLong(qGrabs.TextContent); release.Seeders = ParseUtil.CoerceInt(qSeeders.TextContent); release.Peers = ParseUtil.CoerceInt(qLeechers.TextContent) + release.Seeders; release.DownloadVolumeFactor = qFreeLeech != null ? 0 : 1; release.UploadVolumeFactor = 1; releases.Add(release); } catch (Exception ex) { logger.Error($"{ID}: Error while parsing row '{row.OuterHtml}': {ex.Message}"); } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); searchBlock Search = Definition.Search; // init template context var variables = getTemplateVariablesFromConfigData(); variables[".Query.Type"] = query.QueryType; variables[".Query.Q"] = query.SearchTerm; variables[".Query.Series"] = null; variables[".Query.Ep"] = query.Episode; variables[".Query.Season"] = query.Season; variables[".Query.Movie"] = null; variables[".Query.Year"] = null; variables[".Query.Limit"] = query.Limit; variables[".Query.Offset"] = query.Offset; variables[".Query.Extended"] = query.Extended; variables[".Query.Categories"] = query.Categories; variables[".Query.APIKey"] = query.ApiKey; variables[".Query.TVDBID"] = null; variables[".Query.TVRageID"] = query.RageID; variables[".Query.IMDBID"] = query.ImdbID; variables[".Query.TVMazeID"] = null; variables[".Query.TraktID"] = null; variables[".Query.Episode"] = query.GetEpisodeSearchString(); variables[".Categories"] = MapTorznabCapsToTrackers(query); var KeywordTokens = new List <string>(); var KeywordTokenKeys = new List <string> { "Q", "Series", "Movie", "Year" }; foreach (var key in KeywordTokenKeys) { var Value = (string)variables[".Query." + key]; if (!string.IsNullOrWhiteSpace(Value)) { KeywordTokens.Add(Value); } } if (!string.IsNullOrWhiteSpace((string)variables[".Query.Episode"])) { KeywordTokens.Add((string)variables[".Query.Episode"]); } variables[".Query.Keywords"] = string.Join(" ", KeywordTokens); variables[".Keywords"] = variables[".Query.Keywords"]; // build search URL var searchUrl = SiteLink + applyGoTemplateText(Search.Path, variables) + "?"; var queryCollection = new NameValueCollection(); if (Search.Inputs != null) { foreach (var Input in Search.Inputs) { var value = applyGoTemplateText(Input.Value, variables); if (Input.Key == "$raw") { searchUrl += value; } else { queryCollection.Add(Input.Key, value); } } } searchUrl += "&" + queryCollection.GetQueryString(); // send HTTP request var response = await RequestBytesWithCookies(searchUrl); var results = Encoding.GetEncoding("iso-8859-1").GetString(response.Content); try { var SearchResultParser = new HtmlParser(); var SearchResultDocument = SearchResultParser.Parse(results); var Rows = SearchResultDocument.QuerySelectorAll(Search.Rows.Selector); foreach (var Row in Rows) { try { var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 48 * 60 * 60; // Parse fields foreach (var Field in Search.Fields) { string value = handleSelector(Field.Value, Row); value = ParseUtil.NormalizeSpace(value); try { switch (Field.Key) { case "download": release.Link = resolvePath(value); break; case "details": var url = resolvePath(value); release.Guid = url; if (release.Comments == null) { release.Comments = url; } break; case "comments": release.Comments = resolvePath(value); break; case "title": release.Title = value; break; case "description": release.Description = value; break; case "category": release.Category = MapTrackerCatToNewznab(value); break; case "size": release.Size = ReleaseInfo.GetBytes(value); break; case "leechers": if (release.Peers == null) { release.Peers = ParseUtil.CoerceInt(value); } else { release.Peers += ParseUtil.CoerceInt(value); } break; case "seeders": release.Seeders = ParseUtil.CoerceInt(value); if (release.Peers == null) { release.Peers = release.Seeders; } else { release.Peers += release.Seeders; } break; case "date": release.PublishDate = DateTimeUtil.FromUnknown(value); break; case "files": release.Files = ParseUtil.CoerceLong(value); break; case "grabs": release.Grabs = ParseUtil.CoerceLong(value); break; case "downloadvolumefactor": release.DownloadVolumeFactor = ParseUtil.CoerceDouble(value); break; case "uploadvolumefactor": release.UploadVolumeFactor = ParseUtil.CoerceDouble(value); break; default: break; } } catch (Exception ex) { throw new Exception(string.Format("Error while parsing field={0}, selector={1}, value={2}: {3}", Field.Key, Field.Value.Selector, value, ex.Message)); } } releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("CardigannIndexer ({0}): Error while parsing row '{1}': {2}", ID, Row.OuterHtml, ex)); } } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); // the order of the params is important! var qc = new List <string>(); var catList = MapTorznabCapsToTrackers(query); foreach (var cat in catList) { qc.Add(cat); } if (query.IsImdbQuery) { qc.Add("deep"); qc.Add("q"); qc.Add(query.ImdbID); } else { qc.Add("q"); qc.Add(WebUtilityHelpers.UrlEncode(query.GetQueryString(), Encoding)); } var searchUrl = SearchUrl + string.Join("/", qc); var response = await RequestWithCookiesAndRetryAsync(searchUrl); if (!response.ContentString.Contains("/logout.php")) // re-login { await DoLogin(); response = await RequestWithCookiesAndRetryAsync(searchUrl); } try { var parser = new HtmlParser(); var dom = parser.ParseDocument(response.ContentString); var rows = dom.QuerySelectorAll("div.boxContent > table > tbody > tr"); foreach (var row in rows) { var cells = row.QuerySelectorAll("td"); var title = row.QuerySelector("td[class='lft'] > div > a").TextContent.Trim(); var link = new Uri(SiteLink + row.QuerySelector("img[title='Download']").ParentElement.GetAttribute("href").TrimStart('/')); var comments = new Uri(SiteLink + row.QuerySelector("td[class='lft'] > div > a").GetAttribute("href").TrimStart('/')); var size = ReleaseInfo.GetBytes(cells[5].TextContent); var grabs = ParseUtil.CoerceInt(cells[6].TextContent); var seeders = ParseUtil.CoerceInt(cells[7].TextContent); var leechers = ParseUtil.CoerceInt(cells[8].TextContent); var pubDateStr = row.QuerySelector("span[class^='elapsedDate']").GetAttribute("title").Replace(" at", ""); var publishDate = DateTime.ParseExact(pubDateStr, "dddd, MMMM d, yyyy h:mmtt", CultureInfo.InvariantCulture); var cat = row.QuerySelector("a").GetAttribute("href").Split('/').Last(); var downloadVolumeFactor = row.QuerySelector("span:contains(\"[Freeleech]\")") != null ? 0 : 1; var release = new ReleaseInfo { Title = title, Link = link, Guid = link, Comments = comments, PublishDate = publishDate, Category = MapTrackerCatToNewznab(cat), Size = size, Grabs = grabs, Seeders = seeders, Peers = seeders + leechers, MinimumRatio = 1, MinimumSeedTime = 172800, // 48 hours DownloadVolumeFactor = downloadVolumeFactor, UploadVolumeFactor = 1 }; releases.Add(release); } } catch (Exception ex) { OnParseError(response.ContentString, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); // if the search string is empty use the "last 24h torrents" view if (string.IsNullOrWhiteSpace(searchString)) { var results = await RequestStringWithCookies(TodayUrl); try { string RowsSelector = "table.torrent_table > tbody > tr:not(tr.colhead)"; var SearchResultParser = new HtmlParser(); var SearchResultDocument = SearchResultParser.Parse(results.Content); var Rows = SearchResultDocument.QuerySelectorAll(RowsSelector); foreach (var Row in Rows) { try { var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 0; var qDetailsLink = Row.QuerySelector("a.BJinfoBox"); var qTitle = qDetailsLink.QuerySelector("font"); release.Title = qTitle.TextContent; var qBJinfoBox = qDetailsLink.QuerySelector("span"); var qCatLink = Row.QuerySelector("a[href^=\"/torrents.php?filter_cat\"]"); var qDLLink = Row.QuerySelector("a[href^=\"torrents.php?action=download\"]"); var qSeeders = Row.QuerySelector("td:nth-child(4)"); var qLeechers = Row.QuerySelector("td:nth-child(5)"); var qFreeLeech = Row.QuerySelector("font[color=\"green\"]:contains(Free)"); release.Description = ""; foreach (var Child in qBJinfoBox.ChildNodes) { var type = Child.NodeType; if (type != NodeType.Text) { continue; } var line = Child.TextContent; if (line.StartsWith("Tamanho:")) { string Size = line.Substring("Tamanho: ".Length);; release.Size = ReleaseInfo.GetBytes(Size); } else if (line.StartsWith("Lançado em: ")) { string PublishDateStr = line.Substring("Lançado em: ".Length).Replace("às ", ""); PublishDateStr += " +0"; var PublishDate = DateTime.SpecifyKind(DateTime.ParseExact(PublishDateStr, "dd/MM/yyyy HH:mm z", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); release.PublishDate = PublishDate.ToLocalTime(); } else { release.Description += line + "\n"; } } var catStr = qCatLink.GetAttribute("href").Split('=')[1]; release.Category = MapTrackerCatToNewznab(catStr); release.Link = new Uri(SiteLink + qDLLink.GetAttribute("href")); release.Comments = new Uri(SiteLink + qDetailsLink.GetAttribute("href")); release.Guid = release.Link; release.Seeders = ParseUtil.CoerceInt(qSeeders.TextContent); release.Peers = ParseUtil.CoerceInt(qLeechers.TextContent) + release.Seeders; if (qFreeLeech != null) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("{0}: Error while parsing row '{1}': {2}", ID, Row.OuterHtml, ex.Message)); } } } catch (Exception ex) { OnParseError(results.Content, ex); } } else // use search { var searchUrl = BrowseUrl; var queryCollection = new NameValueCollection(); queryCollection.Add("searchstr", StripSearchString(searchString)); queryCollection.Add("order_by", "time"); queryCollection.Add("order_way", "desc"); queryCollection.Add("group_results", "1"); queryCollection.Add("action", "basic"); queryCollection.Add("searchsubmit", "1"); foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add("filter_cat[" + cat + "]", "1"); } searchUrl += "?" + queryCollection.GetQueryString(); var results = await RequestStringWithCookies(searchUrl); try { string RowsSelector = "table.torrent_table > tbody > tr:not(tr.colhead)"; var SearchResultParser = new HtmlParser(); var SearchResultDocument = SearchResultParser.Parse(results.Content); var Rows = SearchResultDocument.QuerySelectorAll(RowsSelector); ICollection <int> GroupCategory = null; string GroupTitle = null; string GroupYearStr = null; Nullable <DateTime> GroupPublishDate = null; foreach (var Row in Rows) { try { var qDetailsLink = Row.QuerySelector("a[href^=\"torrents.php?id=\"]"); string Title = qDetailsLink.TextContent; ICollection <int> Category = null; string YearStr = null; Nullable <DateTime> YearPublishDate = null; if (Row.ClassList.Contains("group") || Row.ClassList.Contains("torrent")) // group/ungrouped headers { var qCatLink = Row.QuerySelector("a[href^=\"/torrents.php?filter_cat\"]"); string CategoryStr = qCatLink.GetAttribute("href").Split('=')[1].Split('&')[0]; Category = MapTrackerCatToNewznab(CategoryStr); YearStr = qDetailsLink.NextSibling.TextContent.Trim().TrimStart('[').TrimEnd(']'); YearPublishDate = DateTime.SpecifyKind(DateTime.ParseExact(YearStr, "yyyy", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); if (Row.ClassList.Contains("group")) // group headers { GroupCategory = Category; GroupTitle = Title; GroupYearStr = YearStr; GroupPublishDate = YearPublishDate; continue; } } var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 0; var qDLLink = Row.QuerySelector("a[href^=\"torrents.php?action=download\"]"); var qSize = Row.QuerySelector("td:nth-last-child(4)"); var qSeeders = Row.QuerySelector("td:nth-last-child(3)"); var qLeechers = Row.QuerySelector("td:nth-last-child(2)"); var qFreeLeech = Row.QuerySelector("strong[title=\"Free\"]"); if (Row.ClassList.Contains("group_torrent")) // torrents belonging to a group { release.Description = qDetailsLink.TextContent; release.Title = GroupTitle + " " + GroupYearStr; release.PublishDate = GroupPublishDate.Value; release.Category = GroupCategory; } else if (Row.ClassList.Contains("torrent")) // standalone/un grouped torrents { var qDescription = Row.QuerySelector("div.torrent_info"); release.Description = qDescription.TextContent; release.Title = Title + " " + YearStr; release.PublishDate = YearPublishDate.Value; release.Category = Category; } release.Description = release.Description.Replace(" / Free", ""); // Remove Free Tag release.Description = release.Description.Replace("Full HD", "1080p"); release.Description = release.Description.Replace("/ HD / ", "/ 720p /"); release.Description = release.Description.Replace(" / HD]", " / 720p]"); release.Description = release.Description.Replace("4K", "2160p"); int nBarra = release.Title.IndexOf("["); if (nBarra != -1) { release.Title = release.Title.Substring(nBarra + 1); release.Title = release.Title.Replace("]", ""); } release.Title += " " + release.Description; // add year and Description to the release Title to add some meaning to it // check for previously stripped search terms if (!query.MatchQueryStringAND(release.Title)) { continue; } var Size = qSize.TextContent; release.Size = ReleaseInfo.GetBytes(Size); release.Link = new Uri(SiteLink + qDLLink.GetAttribute("href")); release.Comments = new Uri(SiteLink + qDetailsLink.GetAttribute("href")); release.Guid = release.Link; release.Seeders = ParseUtil.CoerceInt(qSeeders.TextContent); release.Peers = ParseUtil.CoerceInt(qLeechers.TextContent) + release.Seeders; if (qFreeLeech != null) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("{0}: Error while parsing row '{1}': {2}", ID, Row.OuterHtml, ex.Message)); } } } catch (Exception ex) { OnParseError(results.Content, ex); } } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { TimeZoneInfo.TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 3, 0, 0), 3, 5, DayOfWeek.Sunday); TimeZoneInfo.TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 4, 0, 0), 10, 5, DayOfWeek.Sunday); TimeSpan delta = new TimeSpan(1, 0, 0); TimeZoneInfo.AdjustmentRule adjustment = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(new DateTime(1999, 10, 1), DateTime.MaxValue.Date, delta, startTransition, endTransition); TimeZoneInfo.AdjustmentRule[] adjustments = { adjustment }; TimeZoneInfo romaniaTz = TimeZoneInfo.CreateCustomTimeZone("Romania Time", new TimeSpan(2, 0, 0), "(GMT+02:00) Romania Time", "Romania Time", "Romania Daylight Time", adjustments); var releases = new List <ReleaseInfo>(); string episodeSearchUrl; if (string.IsNullOrEmpty(query.GetQueryString())) { episodeSearchUrl = SearchUrl; } else { episodeSearchUrl = $"{SearchUrl}?search={HttpUtility.UrlEncode(query.GetQueryString())}&cat=0"; } var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); try { CQ dom = results.Content; var rows = dom["#highlight > tbody > tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); var qRow = row.Cq(); var qLink = qRow.Find("a.torrent_name_link").First(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; release.Title = qLink.Attr("title"); if (!query.MatchQueryStringAND(release.Title)) { continue; } release.Description = release.Title; release.Guid = new Uri(SiteLink + qLink.Attr("href").TrimStart('/')); release.Comments = release.Guid; release.Link = new Uri(SiteLink + qRow.Find("td.table_links > a").First().Attr("href").TrimStart('/')); release.Category = TvCategoryParser.ParseTvShowQuality(release.Title); release.Seeders = ParseUtil.CoerceInt(qRow.Find("td.table_seeders").Text().Trim()); release.Peers = ParseUtil.CoerceInt(qRow.Find("td.table_leechers").Text().Trim()) + release.Seeders; var sizeStr = qRow.Find("td.table_size")[0].Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); DateTime pubDateRomania; var dateString = qRow.Find("td.table_added").Text().Trim(); if (dateString.StartsWith("Today ")) { pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]); } else if (dateString.StartsWith("Yesterday ")) { pubDateRomania = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateString.Split(' ')[1]) - TimeSpan.FromDays(1); } else { pubDateRomania = DateTime.SpecifyKind(DateTime.ParseExact(dateString, "d-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); } DateTime pubDateUtc = TimeZoneInfo.ConvertTimeToUtc(pubDateRomania, romaniaTz); release.PublishDate = pubDateUtc.ToLocalTime(); var grabs = row.Cq().Find("td.table_snatch").Get(0).FirstChild.ToString(); release.Grabs = ParseUtil.CoerceInt(grabs); if (row.Cq().Find("img[alt=\"100% Free\"]").Any()) { release.DownloadVolumeFactor = 0; } else if (row.Cq().Find("img[alt=\"50% Free\"]").Any()) { release.DownloadVolumeFactor = 0.5; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { List <ReleaseInfo> releases = new List <ReleaseInfo>(); List <string> searchStrings = new List <string>(new string[] { query.GetQueryString() }); if (string.IsNullOrEmpty(query.Episode) && (query.Season > 0)) { // Tracker naming rules: If query is for a whole season, "Season #" instead of "S##". searchStrings.Add((query.SanitizedSearchTerm + " " + string.Format("\"Season {0}\"", query.Season)).Trim()); } List <string> categories = MapTorznabCapsToTrackers(query); List <string> request_urls = new List <string>(); foreach (var searchString in searchStrings) { var queryCollection = new NameValueCollection(); queryCollection.Add("action", "basic"); if (!string.IsNullOrWhiteSpace(searchString)) { queryCollection.Add("searchstr", searchString); } foreach (var cat in categories) { queryCollection.Add("filter_cat[" + cat + "]", "1"); } request_urls.Add(SearchUrl + queryCollection.GetQueryString()); } IEnumerable <Task <WebClientStringResult> > downloadTasksQuery = from url in request_urls select RequestStringWithCookiesAndRetry(url); WebClientStringResult[] responses = await Task.WhenAll(downloadTasksQuery.ToArray()); for (int i = 0; i < searchStrings.Count(); i++) { var results = responses[i]; // Occasionally the cookies become invalid, login again if that happens if (results.IsRedirect) { await ApplyConfiguration(null); results = await RequestStringWithCookiesAndRetry(request_urls[i]); } try { CQ dom = results.Content; var rows = dom["#torrent_table > tbody > tr.torrent"]; foreach (var row in rows) { CQ qRow = row.Cq(); var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; var catStr = row.ChildElements.ElementAt(0).FirstElementChild.GetAttribute("href").Split(new char[] { '[', ']' })[1]; release.Category = MapTrackerCatToNewznab(catStr); var qLink = row.ChildElements.ElementAt(1).Cq().Children("a")[0].Cq(); var linkStr = qLink.Attr("href"); release.Comments = new Uri(BaseUrl + "/" + linkStr); release.Guid = release.Comments; var qDownload = row.ChildElements.ElementAt(1).Cq().Find("a[title='Download']")[0].Cq(); release.Link = new Uri(BaseUrl + "/" + qDownload.Attr("href")); var dateStr = row.ChildElements.ElementAt(3).Cq().Text().Trim().Replace(" and", ""); release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); var sizeStr = row.ChildElements.ElementAt(4).Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Files = ParseUtil.CoerceInt(row.ChildElements.ElementAt(2).Cq().Text().Trim()); release.Grabs = ParseUtil.CoerceInt(row.ChildElements.ElementAt(6).Cq().Text().Trim()); release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text().Trim()); release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text().Trim()) + release.Seeders; var grabs = qRow.Find("td:nth-child(6)").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); if (qRow.Find("strong:contains(\"Freeleech!\")").Length >= 1) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; var title = qRow.Find("td:nth-child(2)"); title.Find("span, strong, div, br").Remove(); release.Title = ParseUtil.NormalizeMultiSpaces(title.Text().Replace(" - ]", "]")); if (catStr == "10") //change "Season #" to "S##" for TV shows { release.Title = Regex.Replace(release.Title, @"Season (\d+)", m => string.Format("S{0:00}", Int32.Parse(m.Groups[1].Value))); } releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var qc = new NameValueCollection { { "incldead", "1" } }; if (query.IsImdbQuery) { qc.Add("search", query.ImdbID); qc.Add("sc", "2"); // search in description } else { qc.Add("search", query.GetQueryString()); qc.Add("sc", "1"); // search in title } foreach (var cat in MapTorznabCapsToTrackers(query)) { qc.Add("c" + cat, "1"); } var searchUrl = SearchUrl + "?" + qc.GetQueryString(); var response = await RequestWithCookiesAndRetryAsync(searchUrl, referer : SearchUrl); if (response.IsRedirect) // re-login { await ApplyConfiguration(null); response = await RequestWithCookiesAndRetryAsync(searchUrl, referer : SearchUrl); } try { var parser = new HtmlParser(); var dom = parser.ParseDocument(response.ContentString); var rows = dom.QuerySelectorAll("table > tbody:has(tr > td.colhead) > tr:not(:has(td.colhead))"); foreach (var row in rows) { var release = new ReleaseInfo(); var link = row.QuerySelector("td:nth-of-type(2) a:nth-of-type(2)"); release.Guid = new Uri(SiteLink + link.GetAttribute("href")); release.Comments = release.Guid; release.Title = link.GetAttribute("title"); // There isn't a title attribute if the release name isn't truncated. if (string.IsNullOrWhiteSpace(release.Title)) { release.Title = link.FirstChild.TextContent.Trim(); } release.Description = release.Title; // If we search an get no results, we still get a table just with no info. if (string.IsNullOrWhiteSpace(release.Title)) { break; } // Check if the release has been assigned a category var qCat = row.QuerySelector("td:nth-of-type(1) a"); if (qCat != null) { var cat = qCat.GetAttribute("href").Substring(15); release.Category = MapTrackerCatToNewznab(cat); } var qLink = row.QuerySelector("td:nth-of-type(2) a"); release.Link = new Uri(SiteLink + qLink.GetAttribute("href")); var added = row.QuerySelector("td:nth-of-type(5)").TextContent.Trim(); release.PublishDate = DateTime.ParseExact(added, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture); var sizeStr = row.QuerySelector("td:nth-of-type(7)").TextContent.Trim(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)").TextContent.Trim()); release.Peers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)").TextContent.Trim()) + release.Seeders; var files = row.QuerySelector("td:nth-child(3)").TextContent; release.Files = ParseUtil.CoerceInt(files); var grabs = row.QuerySelector("td:nth-child(8)").TextContent; if (grabs != "----") { release.Grabs = ParseUtil.CoerceInt(grabs); } release.DownloadVolumeFactor = row.QuerySelector("font[color=\"green\"]:contains(\"F\"):contains(\"L\")") != null ? 0 : 1; release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(response.ContentString, ex); } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var queryUrl = SearchUrl; var queryCollection = new NameValueCollection(); var cats = MapTorznabCapsToTrackers(query); var tags = string.Empty; var catGroups = new List <string>(); foreach (var cat in cats) { //"cat[]=7&tags=x264" var cSplit = cat.Split('&'); if (cSplit.Length > 0) { var gsplit = cSplit[0].Split('='); if (gsplit.Length > 1) { catGroups.Add(gsplit[1]); } } if (cSplit.Length > 1) { var gsplit = cSplit[1].Split('='); if (gsplit.Length > 1) { if (tags != string.Empty) { tags += ","; } tags += gsplit[1]; } } } if (catGroups.Distinct().Count() == 1) { queryCollection.Add("cat[]", catGroups.First()); } if (!string.IsNullOrWhiteSpace(query.GetQueryString())) { queryCollection.Add("st", "1"); queryCollection.Add("search", query.GetQueryString()); } // Do not include too many tags as it'll mess with their servers. if (tags.Split(',').Length < 7) { queryCollection.Add("tags", tags); if (!string.IsNullOrWhiteSpace(tags)) { // if tags are specified match any queryCollection.Add("tf", "any"); } else { // if no tags are specified match all, with any we get random results queryCollection.Add("tf", "all"); } } if (queryCollection.Count > 0) { queryUrl += "?" + queryCollection.GetQueryString(); } var response = await RequestStringWithCookiesAndRetry(queryUrl); if (response.IsRedirect) { await ApplyConfiguration(null); response = await RequestStringWithCookiesAndRetry(queryUrl); } try { CQ dom = response.Content; var rows = dom["table > tbody > tr.browse"]; foreach (var row in rows) { CQ qRow = row.Cq(); var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; var qLink = row.ChildElements.ElementAt(1).Cq().Find("a").First(); release.Title = qLink.Text().Trim(); if (qLink.Find("span").Count() == 1 && release.Title.StartsWith("NEW! |")) { release.Title = release.Title.Substring(6).Trim(); } release.Comments = new Uri(SiteLink + qLink.Attr("href")); release.Guid = release.Comments; var qDownload = row.ChildElements.ElementAt(2).Cq().Find("a").First(); release.Link = new Uri(SiteLink + qDownload.Attr("href")); var dateStr = row.ChildElements.ElementAt(5).InnerHTML.Replace("<br>", " "); release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr); var sizeStr = row.ChildElements.ElementAt(7).Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(9).InnerText); release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(10).InnerText) + release.Seeders; var cat = row.ChildElements.ElementAt(0).ChildElements.ElementAt(0).GetAttribute("href").Replace("browse.php?", string.Empty); release.Category = MapTrackerCatToNewznab(cat); var files = qRow.Find("td:nth-child(4)").Text(); release.Files = ParseUtil.CoerceInt(files); var grabs = qRow.Find("td:nth-child(9)").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); release.DownloadVolumeFactor = 0; // ratioless release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(response.Content, ex); } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); searchBlock Search = Definition.Search; // init template context var variables = getTemplateVariablesFromConfigData(); variables[".Query.Type"] = query.QueryType; variables[".Query.Q"] = query.SearchTerm; variables[".Query.Series"] = null; variables[".Query.Ep"] = query.Episode; variables[".Query.Season"] = query.Season; variables[".Query.Movie"] = null; variables[".Query.Year"] = null; variables[".Query.Limit"] = query.Limit; variables[".Query.Offset"] = query.Offset; variables[".Query.Extended"] = query.Extended; variables[".Query.Categories"] = query.Categories; variables[".Query.APIKey"] = query.ApiKey; variables[".Query.TVDBID"] = null; variables[".Query.TVRageID"] = query.RageID; variables[".Query.IMDBID"] = query.ImdbID; variables[".Query.TVMazeID"] = null; variables[".Query.TraktID"] = null; variables[".Query.Episode"] = query.GetEpisodeSearchString(); variables[".Categories"] = MapTorznabCapsToTrackers(query); var KeywordTokens = new List <string>(); var KeywordTokenKeys = new List <string> { "Q", "Series", "Movie", "Year" }; foreach (var key in KeywordTokenKeys) { var Value = (string)variables[".Query." + key]; if (!string.IsNullOrWhiteSpace(Value)) { KeywordTokens.Add(Value); } } if (!string.IsNullOrWhiteSpace((string)variables[".Query.Episode"])) { KeywordTokens.Add((string)variables[".Query.Episode"]); } variables[".Query.Keywords"] = string.Join(" ", KeywordTokens); variables[".Keywords"] = variables[".Query.Keywords"]; // build search URL var searchUrl = SiteLink + applyGoTemplateText(Search.Path, variables) + "?"; var queryCollection = new NameValueCollection(); if (Search.Inputs != null) { foreach (var Input in Search.Inputs) { var value = applyGoTemplateText(Input.Value, variables); if (Input.Key == "$raw") { searchUrl += value; } else { queryCollection.Add(Input.Key, value); } } } searchUrl += "&" + queryCollection.GetQueryString(); // send HTTP request var response = await RequestStringWithCookies(searchUrl); var results = response.Content; try { var SearchResultParser = new HtmlParser(); var SearchResultDocument = SearchResultParser.Parse(results); // check if we need to login again var loginNeeded = CheckIfLoginIsNeeded(response, SearchResultDocument); if (loginNeeded) { logger.Info(string.Format("CardigannIndexer ({0}): Relogin required", ID)); await DoLogin(); await TestLogin(); response = await RequestStringWithCookies(searchUrl); results = results = response.Content; SearchResultDocument = SearchResultParser.Parse(results); } var RowsDom = SearchResultDocument.QuerySelectorAll(Search.Rows.Selector); List <IElement> Rows = new List <IElement>(); foreach (var RowDom in RowsDom) { Rows.Add(RowDom); } // merge following rows for After selector var After = Definition.Search.Rows.After; if (After > 0) { for (int i = 0; i < Rows.Count; i += 1) { var CurrentRow = Rows[i]; for (int j = 0; j < After; j += 1) { var MergeRowIndex = i + j + 1; var MergeRow = Rows[MergeRowIndex]; List <INode> MergeNodes = new List <INode>(); foreach (var node in MergeRow.QuerySelectorAll("td")) { MergeNodes.Add(node); } CurrentRow.Append(MergeNodes.ToArray()); } Rows.RemoveRange(i + 1, After); } } foreach (var Row in Rows) { try { var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 48 * 60 * 60; // Parse fields foreach (var Field in Search.Fields) { string value = null; try { value = handleSelector(Field.Value, Row); value = ParseUtil.NormalizeSpace(value); switch (Field.Key) { case "download": if (value.StartsWith("magnet:")) { release.MagnetUri = new Uri(value); release.Link = release.MagnetUri; } else { release.Link = resolvePath(value); } break; case "details": var url = resolvePath(value); release.Guid = url; release.Comments = url; if (release.Guid == null) { release.Guid = url; } break; case "comments": var CommentsUrl = resolvePath(value); if (release.Comments == null) { release.Comments = CommentsUrl; } if (release.Guid == null) { release.Guid = CommentsUrl; } break; case "title": release.Title = value; break; case "description": release.Description = value; break; case "category": release.Category = MapTrackerCatToNewznab(value); break; case "size": release.Size = ReleaseInfo.GetBytes(value); break; case "leechers": if (release.Peers == null) { release.Peers = ParseUtil.CoerceInt(value); } else { release.Peers += ParseUtil.CoerceInt(value); } break; case "seeders": release.Seeders = ParseUtil.CoerceInt(value); if (release.Peers == null) { release.Peers = release.Seeders; } else { release.Peers += release.Seeders; } break; case "date": release.PublishDate = DateTimeUtil.FromUnknown(value); break; case "files": release.Files = ParseUtil.CoerceLong(value); break; case "grabs": release.Grabs = ParseUtil.CoerceLong(value); break; case "downloadvolumefactor": release.DownloadVolumeFactor = ParseUtil.CoerceDouble(value); break; case "uploadvolumefactor": release.UploadVolumeFactor = ParseUtil.CoerceDouble(value); break; case "imdb": Regex IMDBRegEx = new Regex(@"(\d+)", RegexOptions.Compiled); var IMDBMatch = IMDBRegEx.Match(value); var IMDBId = IMDBMatch.Groups[1].Value; release.Imdb = ParseUtil.CoerceLong(IMDBId); break; default: break; } } catch (Exception ex) { if (OptionalFileds.Contains(Field.Key)) { continue; } throw new Exception(string.Format("Error while parsing field={0}, selector={1}, value={2}: {3}", Field.Key, Field.Value.Selector, value, ex.Message)); } } // if DateHeaders is set go through the previous rows and look for the header selector var DateHeaders = Definition.Search.Rows.Dateheaders; if (release.PublishDate == null && DateHeaders != null) { var PrevRow = Row.PreviousElementSibling; string value = null; while (PrevRow != null) { try { value = handleSelector(DateHeaders, PrevRow); break; } catch (Exception) { // do nothing } PrevRow = PrevRow.PreviousElementSibling; } if (value == null) { throw new Exception(string.Format("No date header row found for {0}", release.ToString())); } release.PublishDate = DateTimeUtil.FromUnknown(value); } releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("CardigannIndexer ({0}): Error while parsing row '{1}': {2}", ID, Row.OuterHtml, ex)); } } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); // API doesn't support getting the latest torrents, searching for the empty string will cause an error and all torrents returned var searchUrl = SearchUrl + HttpUtility.UrlEncode(query.SanitizedSearchTerm).Replace("+", "%20"); searchUrl += "?offset=0&limit=200&cat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Key; // handle special term search for tvsearch var queryStringOverride = query.SanitizedSearchTerm; switch (query.QueryType) { case "tvsearch": // T411 make the difference beetween Animation Movies and TV Animation Series, while Torznab does not. // So here we take LastOrDefault from the category ids, so if the query specify an animation tv serie, we select the correct id. searchUrl += "&subcat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Value.LastOrDefault(); if (query.Season >= 1 && query.Season <= 30) { var seasonTermValue = 967 + query.Season; searchUrl += "&term[45][]=" + seasonTermValue; queryStringOverride += " " + query.Season; } if (query.Episode != null) { int episodeInt; int episodeCategoryOffset = 936; ParseUtil.TryCoerceInt(query.Episode, out episodeInt); if (episodeInt >= 1 && episodeInt <= 8) { episodeCategoryOffset = 936; } else if (episodeInt >= 9 && episodeInt <= 30) { episodeCategoryOffset = 937; } else if (episodeInt >= 31) { episodeCategoryOffset = 1057; } searchUrl += "&term[46][]=" + (episodeCategoryOffset + episodeInt); queryStringOverride += " " + query.Episode; } break; case "movie": // T411 make the difference beetween Animation Movies and TV Animation Series, while Torznab does not. // So here we take FirstOrDefault from the category ids, so if the query specify an animation movie, we select the correct id. searchUrl += "&subcat=" + GetCategoryFromSubCat(query.Categories.FirstOrDefault()).Value.FirstOrDefault(); break; } var headers = new Dictionary <string, string>(); headers.Add("Authorization", await GetAuthToken()); var response = await RequestStringWithCookies(searchUrl, null, null, headers); var results = response.Content; var jsonStart = results.IndexOf('{'); var jsonLength = results.Length - jsonStart; var jsonResult = JObject.Parse(results.Substring(jsonStart)); try { var items = (JArray)jsonResult["torrents"]; foreach (var item in items) { if (item.GetType() == typeof(JValue)) { logger.Debug(string.Format("{0}: skipping torrent ID {1} (pending release without details)", ID, item.ToString())); continue; } var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; release.DownloadVolumeFactor = 0; release.DownloadVolumeFactor = 1; var torrentId = (string)item["id"]; release.Link = new Uri(DownloadUrl + torrentId); release.Title = (string)item["name"]; if ((query.ImdbID == null || !TorznabCaps.SupportsImdbSearch) && !query.MatchQueryStringAND(release.Title, null, queryStringOverride)) { continue; } if ((string)item["isVerified"] == "1") { release.Description = "Verified"; } release.Comments = new Uri(CommentsUrl + (string)item["rewritename"]); release.Guid = release.Comments; var dateUtc = DateTime.ParseExact((string)item["added"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); release.PublishDate = DateTime.SpecifyKind(dateUtc, DateTimeKind.Utc).ToLocalTime(); release.Seeders = ParseUtil.CoerceInt((string)item["seeders"]); release.Peers = ParseUtil.CoerceInt((string)item["leechers"]) + release.Seeders; release.Size = ParseUtil.CoerceLong((string)item["size"]); release.Category = MapTrackerCatToNewznab((string)item["category"]); release.Grabs = ParseUtil.CoerceLong((string)item["times_completed"]); releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var qc = new NameValueCollection { { "cat", MapTorznabCapsToTrackers(query, true).FirstIfSingleOrDefault("0") } }; var results = new List <WebResult>(); var search = new UriBuilder(SearchUrl); if (query.IsImdbQuery) { qc.Add("search", query.ImdbID); qc.Add("options", "4"); //Search URL field for IMDB link search.Query = qc.GetQueryString(); results.Add(await RequestWithCookiesAndRetryAsync(search.ToString())); qc["Options"] = "1"; //Search Title and Description search.Query = qc.GetQueryString(); results.Add(await RequestWithCookiesAndRetryAsync(search.ToString())); } else { //Site handles empty string on search param. No need to check for IsNullOrEmpty() qc.Add("search", query.GetQueryString()); qc.Add("options", "0"); //Search Title Only search.Query = qc.GetQueryString(); results.Add(await RequestWithCookiesAndRetryAsync(search.ToString())); } var parser = new HtmlParser(); foreach (var result in results) { try { var dom = parser.ParseDocument(result.ContentString); foreach (var child in dom.QuerySelectorAll("#needseed")) { child.Remove(); } var table = dom.QuerySelector("table[align=center] + br + table > tbody"); if (table == null) // No results, so skip this search { continue; } foreach (var row in table.Children.Skip(1)) { var release = new ReleaseInfo(); var qLink = row.Children[2].QuerySelector("a"); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; // 48 hours release.Title = qLink.GetAttribute("title"); var detailsLink = new Uri(qLink.GetAttribute("href")); //Skip irrelevant and duplicate entries if (!query.MatchQueryStringAND(release.Title) || releases.Any(r => r.Guid == detailsLink)) { continue; } release.Files = ParseUtil.CoerceLong(row.Children[3].TextContent); release.Grabs = ParseUtil.CoerceLong(row.Children[7].TextContent); release.Guid = detailsLink; release.Comments = release.Guid; release.Link = new Uri(SiteLink + row.QuerySelector("a[href^=\"download.php\"]").GetAttribute("href")); var catUrl = new Uri(SiteLink + row.Children[1].FirstElementChild.GetAttribute("href")); var catQuery = HttpUtility.ParseQueryString(catUrl.Query); var catNum = catQuery["cat"]; release.Category = MapTrackerCatToNewznab(catNum); var dateString = row.Children[5].TextContent.Trim(); var pubDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture); release.PublishDate = DateTime.SpecifyKind(pubDate, DateTimeKind.Local); var sizeStr = row.Children[6].TextContent; release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(row.Children[8].TextContent.Trim()); release.Peers = ParseUtil.CoerceInt(row.Children[9].TextContent.Trim()) + release.Seeders; switch (row.GetAttribute("bgcolor")) { case "#DDDDDD": release.DownloadVolumeFactor = 1; release.UploadVolumeFactor = 2; break; case "#FFFF99": release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 1; break; case "#CCFF99": release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 2; break; default: release.DownloadVolumeFactor = 1; release.UploadVolumeFactor = 1; break; } releases.Add(release); } } catch (Exception ex) { OnParseError(result.ContentString, ex); } } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); // search in normal + gems view foreach (var view in new string[] { "0", "1" }) { var queryCollection = new NameValueCollection(); queryCollection.Add("view", view); queryCollection.Add("searchtype", "1"); queryCollection.Add("incldead", "1"); if (!string.IsNullOrWhiteSpace(searchString)) { queryCollection.Add("search", searchString); } foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add(string.Format("c{0}", cat), "1"); } var searchUrl = SearchUrl + "?" + queryCollection.GetQueryString(); var results = await RequestStringWithCookiesAndRetry(searchUrl); if (results.IsRedirect) { // re-login await ApplyConfiguration(null); results = await RequestStringWithCookiesAndRetry(searchUrl); } try { CQ dom = results.Content; var rows = dom["#torrentBrowse > table > tbody > tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); CQ qRow = row.Cq(); release.MinimumRatio = 0; release.MinimumSeedTime = 2 * 24 * 60 * 60; var qLink = qRow.Find("a[title][href^=\"details.php?id=\"]"); release.Title = qLink.Attr("title"); release.Guid = new Uri(SiteLink + qLink.Attr("href").TrimStart('/')); release.Comments = release.Guid; qLink = qRow.Children().ElementAt(3).Cq().Children("a").First(); release.Link = new Uri(string.Format("{0}{1}", SiteLink, qLink.Attr("href"))); var catUrl = qRow.Children().ElementAt(0).FirstElementChild.Cq().Attr("href"); var catNum = catUrl.Split(new char[] { '=', '&' })[2].Replace("c", ""); release.Category = MapTrackerCatToNewznab(catNum); var dateString = qRow.Children().ElementAt(6).Cq().Text().Trim(); if (dateString.Contains("ago")) { release.PublishDate = DateTimeUtil.FromTimeAgo(dateString); } else { release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture); } var sizeStr = qRow.Children().ElementAt(7).Cq().Text().Split(new char[] { '/' })[0]; release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(qRow.Children().ElementAt(8).Cq().Text().Split(new char[] { '/' })[0].Trim()); release.Peers = ParseUtil.CoerceInt(qRow.Children().ElementAt(8).Cq().Text().Split(new char[] { '/' })[1].Trim()) + release.Seeders; release.Files = ParseUtil.CoerceLong(qRow.Find("td:nth-child(5)").Text()); release.Grabs = ParseUtil.CoerceLong(qRow.Find("a[href^=\"snatches.php?id=\"]").Text().Split(' ')[0]); release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 1; var desc = qRow.Find("td:nth-child(2)"); desc.Find("a").Remove(); desc.Find("small").Remove(); // Remove release name (if enabled in the user cp) release.Description = desc.Text().Trim(new char[] { '-', ' ' }); releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } } return(releases); }
public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = BrowseUrl; var queryCollection = new NameValueCollection(); if (!string.IsNullOrWhiteSpace(searchString)) { queryCollection.Add("q", searchString); } foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add(cat, string.Empty); } if (queryCollection.Count > 0) { searchUrl += "?" + queryCollection.GetQueryString(); } var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); var results = response.Content; try { CQ dom = results; var rows = dom["table.torrents > tbody > tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); var qRow = row.Cq(); var qTitleLink = qRow.Find("a.t_title").First(); release.Title = qTitleLink.Text().Trim(); // If we search an get no results, we still get a table just with no info. if (string.IsNullOrWhiteSpace(release.Title)) { break; } release.Description = release.Title; release.Guid = new Uri(UseLink + qTitleLink.Attr("href").Substring(1)); release.Comments = release.Guid; var descString = qRow.Find(".t_ctime").Text(); var dateString = descString.Split('|').Last().Trim(); dateString = dateString.Split(new string[] { " by " }, StringSplitOptions.None)[0]; release.PublishDate = DateTimeUtil.FromTimeAgo(dateString); var qLink = row.ChildElements.ElementAt(3).Cq().Children("a"); release.Link = new Uri(UseLink + HttpUtility.UrlEncode(qLink.Attr("href").TrimStart('/'))); var sizeStr = row.ChildElements.ElementAt(5).Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(qRow.Find(".t_seeders").Text().Trim()); release.Peers = ParseUtil.CoerceInt(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders; var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(1); release.Category = MapTrackerCatToNewznab(cat); releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
private async Task <IEnumerable <ReleaseInfo> > GetResults(SearchType searchType, string searchTerm) { var cleanSearchTerm = HttpUtility.UrlEncode(searchTerm); // The result list var releases = new List <ReleaseInfo>(); var queryUrl = searchType == SearchType.Video ? SearchUrl : MusicSearchUrl; // Only include the query bit if its required as hopefully the site caches the non query page if (!string.IsNullOrWhiteSpace(searchTerm)) { queryUrl += string.Format("searchstr={0}&action=advanced&search_type=title&year=&year2=&tags=&tags_type=0&sort=time_added&way=desc&hentai=2&releasegroup=&epcount=&epcount2=&artbooktitle=", cleanSearchTerm); } // Check cache first so we don't query the server for each episode when searching for each episode in a series. lock (cache) { // Remove old cache items CleanCache(); var cachedResult = cache.Where(i => i.Query == queryUrl).FirstOrDefault(); if (cachedResult != null) { return(cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray()); } } // Get the content from the tracker var response = await RequestStringWithCookiesAndRetry(queryUrl); CQ dom = response.Content; // Parse try { var releaseInfo = "S01"; var root = dom.Find(".group_cont"); // We may have got redirected to the series page if we have none of these if (root.Count() == 0) { root = dom.Find(".torrent_table"); } foreach (var series in root) { var seriesCq = series.Cq(); var synonyms = new List <string>(); string mainTitle; if (searchType == SearchType.Video) { mainTitle = seriesCq.Find(".group_title strong a").First().Text().Trim(); } else { mainTitle = seriesCq.Find(".group_title strong").Text().Trim(); } var yearStr = seriesCq.Find(".group_title strong").First().Text().Trim().Replace("]", "").Trim(); int yearIndex = yearStr.LastIndexOf("["); if (yearIndex > -1) { yearStr = yearStr.Substring(yearIndex + 1); } int year = 0; if (!int.TryParse(yearStr, out year)) { year = DateTime.Now.Year; } synonyms.Add(mainTitle); // If the title contains a comma then we can't use the synonyms as they are comma seperated if (!mainTitle.Contains(",")) { var symnomnNames = string.Empty; foreach (var e in seriesCq.Find(".group_statbox li")) { if (e.FirstChild.InnerText == "Synonyms:") { symnomnNames = e.InnerText; } } if (!string.IsNullOrWhiteSpace(symnomnNames)) { foreach (var name in symnomnNames.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) { var theName = name.Trim(); if (!theName.Contains("&#") && !string.IsNullOrWhiteSpace(theName)) { synonyms.Add(theName); } } } } foreach (var title in synonyms) { var releaseRows = seriesCq.Find(".torrent_group tr"); // Skip the first two info rows for (int r = 1; r < releaseRows.Count(); r++) { var row = releaseRows.Get(r); var rowCq = row.Cq(); if (rowCq.HasClass("edition_info")) { releaseInfo = rowCq.Find("td").Text(); if (string.IsNullOrWhiteSpace(releaseInfo)) { // Single episodes alpha - Reported that this info is missing. // It should self correct when availible break; } releaseInfo = releaseInfo.Replace("Episode ", ""); releaseInfo = releaseInfo.Replace("Season ", "S"); releaseInfo = releaseInfo.Trim(); int test = 0; if (InsertSeason && int.TryParse(releaseInfo, out test) && releaseInfo.Length == 1) { releaseInfo = "S01E0" + releaseInfo; } } else if (rowCq.HasClass("torrent")) { var links = rowCq.Find("a"); // Protect against format changes if (links.Count() != 2) { continue; } var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 259200; var downloadLink = links.Get(0); // We dont know this so try to fake based on the release year release.PublishDate = new DateTime(year, 1, 1); release.PublishDate = release.PublishDate.AddDays(Math.Min(DateTime.Now.DayOfYear, 365) - 1); var infoLink = links.Get(1); release.Comments = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href")); release.Guid = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href") + "&nh=" + StringUtil.Hash(title)); // Sonarr should dedupe on this url - allow a url per name. release.Link = new Uri(downloadLink.Attributes.GetAttribute("href"), UriKind.Relative); string category = null; if (searchType == SearchType.Video) { category = seriesCq.Find("a[title=\"View Torrent\"]").Text().Trim(); if (category == "TV Series") { release.Category = TorznabCatType.TVAnime.ID; } // Ignore these categories as they'll cause hell with the matcher // TV Special, OVA, ONA, DVD Special, BD Special if (category == "Movie") { release.Category = TorznabCatType.Movies.ID; } if (category == "Manga" || category == "Oneshot" || category == "Anthology" || category == "Manhwa" || category == "Manhua" || category == "Light Novel") { release.Category = TorznabCatType.BooksComics.ID; } if (category == "Novel" || category == "Artbook") { release.Category = TorznabCatType.BooksComics.ID; } if (category == "Game" || category == "Visual Novel") { var description = rowCq.Find(".torrent_properties a:eq(1)").Text(); if (description.Contains(" PSP ")) { release.Category = TorznabCatType.ConsolePSP.ID; } if (description.Contains("PSX")) { release.Category = TorznabCatType.ConsoleOther.ID; } if (description.Contains(" NES ")) { release.Category = TorznabCatType.ConsoleOther.ID; } if (description.Contains(" PC ")) { release.Category = TorznabCatType.PCGames.ID; } } } if (searchType == SearchType.Audio) { category = seriesCq.Find(".group_img .cat a").Text(); if (category == "Single" || category == "Album" || category == "Compilation" || category == "Soundtrack" || category == "Remix CD") { var description = rowCq.Find(".torrent_properties a:eq(1)").Text(); if (description.Contains(" Lossless ")) { release.Category = TorznabCatType.AudioLossless.ID; } else if (description.Contains("MP3")) { release.Category = TorznabCatType.AudioMP3.ID; } else { release.Category = TorznabCatType.AudioOther.ID; } } } // We dont actually have a release name >.> so try to create one var releaseTags = infoLink.InnerText.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); for (int i = releaseTags.Count - 1; i >= 0; i--) { releaseTags[i] = releaseTags[i].Trim(); if (string.IsNullOrWhiteSpace(releaseTags[i])) { releaseTags.RemoveAt(i); } } var group = releaseTags.Last(); if (group.Contains("(") && group.Contains(")")) { // Skip raws if set if (group.ToLowerInvariant().StartsWith("raw") && !AllowRaws) { continue; } var start = group.IndexOf("("); group = "[" + group.Substring(start + 1, (group.IndexOf(")") - 1) - start) + "] "; } else { group = string.Empty; } var infoString = ""; for (int i = 0; i + 1 < releaseTags.Count(); i++) { if (releaseTags[i] == "Raw" && !AllowRaws) { continue; } infoString += "[" + releaseTags[i] + "]"; } if (category == "Movie") { release.Title = string.Format("{0} {1} {2}{3}", title, year, group, infoString); } else { release.Title = string.Format("{0}{1} {2} {3}", group, title, releaseInfo, infoString); } release.Description = title; var size = rowCq.Find(".torrent_size"); if (size.Count() > 0) { release.Size = ReleaseInfo.GetBytes(size.First().Text()); } // Additional 5 hours per GB release.MinimumSeedTime += (release.Size / 1000000000) * 18000; // Peer info release.Seeders = ParseUtil.CoerceInt(rowCq.Find(".torrent_seeders").Text()); release.Peers = release.Seeders + ParseUtil.CoerceInt(rowCq.Find(".torrent_leechers").Text()); // grabs var grabs = rowCq.Find("td.torrent_snatched").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); // freeleech if (rowCq.Find("img[alt=\"Freeleech!\"]").Length >= 1) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; if (release.Category != 0) { releases.Add(release); } } } } } } catch (Exception ex) { OnParseError(response.Content, ex); } // Add to the cache lock (cache) { cache.Add(new CachedQueryResult(queryUrl, releases)); } return(releases.Select(s => (ReleaseInfo)s.Clone())); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var prevCook = CookieHeader + ""; var searchParams = new Dictionary <string, string> { { "do", "search" }, { "category", "0" }, { "include_dead_torrents", "no" } }; if (query.IsImdbQuery) { searchParams.Add("keywords", query.ImdbID); searchParams.Add("search_type", "t_both"); } else { searchParams.Add("keywords", searchString); searchParams.Add("search_type", "t_name"); } var searchPage = await RequestWithCookiesAndRetryAsync( SearchUrl, CookieHeader, RequestType.POST, null, searchParams); // Occasionally the cookies become invalid, login again if that happens if (searchPage.IsRedirect) { await ApplyConfiguration(null); searchPage = await RequestWithCookiesAndRetryAsync( SearchUrl, CookieHeader, RequestType.POST, null, searchParams); } try { var parser = new HtmlParser(); var dom = parser.ParseDocument(searchPage.ContentString); var rows = dom.QuerySelectorAll("table#sortabletable > tbody > tr:has(div > a[href*=\"details.php?id=\"])"); foreach (var row in rows) { var release = new ReleaseInfo(); var qDetails = row.QuerySelector("div > a[href*=\"details.php?id=\"]"); var qTitle = qDetails; // #7975 release.Title = qTitle.TextContent; release.Guid = new Uri(row.QuerySelector("td:nth-of-type(3) a").GetAttribute("href")); release.Link = release.Guid; release.Comments = new Uri(qDetails.GetAttribute("href")); //08-08-2015 12:51 release.PublishDate = DateTime.ParseExact( row.QuerySelectorAll("td:nth-of-type(2) div").Last().TextContent.Trim(), "dd-MM-yyyy H:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); release.Seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(7)").TextContent); release.Peers = release.Seeders + ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(8)").TextContent.Trim()); release.Size = ReleaseInfo.GetBytes(row.QuerySelector("td:nth-of-type(5)").TextContent.Trim()); var qBanner = row.QuerySelector("td:nth-of-type(2) .tooltip-content img"); if (qBanner != null) { release.BannerUrl = new Uri(qBanner.GetAttribute("src")); } var cat = row.QuerySelector("td:nth-of-type(1) a").GetAttribute("href"); var catSplit = cat.LastIndexOf('='); if (catSplit > -1) { cat = cat.Substring(catSplit + 1); } release.Category = MapTrackerCatToNewznab(cat); var grabs = row.QuerySelector("td:nth-child(6)").TextContent; release.Grabs = ParseUtil.CoerceInt(grabs); if (row.QuerySelector("img[alt^=\"Free Torrent\"]") != null) { release.DownloadVolumeFactor = 0; } else if (row.QuerySelector("img[alt^=\"Silver Torrent\"]") != null) { release.DownloadVolumeFactor = 0.5; } else { release.DownloadVolumeFactor = 1; } if (row.QuerySelector("img[alt^=\"x2 Torrent\"]") != null) { release.UploadVolumeFactor = 2; } else { release.UploadVolumeFactor = 1; } release.MinimumRatio = 0.8; releases.Add(release); } } catch (Exception ex) { OnParseError(searchPage.ContentString, ex); } if (!CookieHeader.Trim().Equals(prevCook.Trim())) { SaveConfig(); } return(releases); }
protected async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query, String seasonep) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var pairs = new List <KeyValuePair <string, string> >(); if (seasonep != null) { searchString = query.SanitizedSearchTerm; } pairs.Add(new KeyValuePair <string, string>("nyit_sorozat_resz", "true")); pairs.Add(new KeyValuePair <string, string>("miben", "name")); pairs.Add(new KeyValuePair <string, string>("tipus", "kivalasztottak_kozott")); pairs.Add(new KeyValuePair <string, string>("submit.x", "1")); pairs.Add(new KeyValuePair <string, string>("submit.y", "1")); pairs.Add(new KeyValuePair <string, string>("submit", "Ok")); pairs.Add(new KeyValuePair <string, string>("mire", searchString)); var cats = MapTorznabCapsToTrackers(query); if (cats.Count == 0) { cats = GetAllTrackerCategories(); } foreach (var lcat in LanguageCats) { if (!configData.Hungarian.Value) { cats.Remove(lcat + "_hun"); } if (!configData.English.Value) { cats.Remove(lcat); } } foreach (var cat in cats) { pairs.Add(new KeyValuePair <string, string>("kivalasztott_tipus[]", cat)); } var results = await PostDataWithCookiesAndRetry(SearchUrl, pairs); try { CQ dom = results.Content; ReleaseInfo release; var rows = dom[".box_torrent_all"].Find(".box_torrent"); foreach (var row in rows) { CQ qRow = row.Cq(); var key = dom["link[rel=alternate]"].First().Attr("href").Split('=').Last(); release = new ReleaseInfo(); var torrentTxt = qRow.Find(".torrent_txt, .torrent_txt2").Find("a").Get(0); //if (torrentTxt == null) continue; release.Title = torrentTxt.GetAttribute("title"); release.Description = qRow.Find("div.siterank").Text(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 1; string downloadLink = SiteLink + torrentTxt.GetAttribute("href"); string downloadId = downloadLink.Substring(downloadLink.IndexOf("&id=") + 4); release.Link = new Uri(SiteLink.ToString() + "torrents.php?action=download&id=" + downloadId + "&key=" + key); release.Comments = new Uri(SiteLink.ToString() + "torrents.php?action=details&id=" + downloadId); release.Guid = new Uri(release.Comments.ToString() + "#comments");; release.Seeders = ParseUtil.CoerceInt(qRow.Find(".box_s2").Find("a").First().Text()); release.Peers = ParseUtil.CoerceInt(qRow.Find(".box_l2").Find("a").First().Text()) + release.Seeders; var imdblink = qRow.Find("a[href*=\".imdb.com/title\"]").Attr("href"); release.Imdb = ParseUtil.GetLongFromString(imdblink); var banner = qRow.Find("img.infobar_ico").Attr("onmouseover"); if (banner != null) { Regex BannerRegEx = new Regex(@"mutat\('(.*?)', '", RegexOptions.Compiled); var BannerMatch = BannerRegEx.Match(banner); var bannerurl = BannerMatch.Groups[1].Value; release.BannerUrl = new Uri(bannerurl); } release.PublishDate = DateTime.Parse(qRow.Find(".box_feltoltve2").Get(0).InnerHTML.Replace("<br />", " "), CultureInfo.InvariantCulture); string[] sizeSplit = qRow.Find(".box_meret2").Get(0).InnerText.Split(' '); release.Size = ReleaseInfo.GetBytes(sizeSplit[1].ToLower(), ParseUtil.CoerceFloat(sizeSplit[0])); string catlink = qRow.Find("a:has(img[class='categ_link'])").First().Attr("href"); string cat = ParseUtil.GetArgumentFromQueryString(catlink, "tipus"); release.Category = MapTrackerCatToNewznab(cat); if (seasonep == null) { releases.Add(release); } else { if (query.MatchQueryStringAND(release.Title, null, seasonep)) { /* For sonnar if the search querry was english the title must be english also so we need to change the Description and Title */ var temp = release.Title; // releasedata everithing after Name.S0Xe0X String releasedata = release.Title.Split(new[] { seasonep }, StringSplitOptions.None)[1].Trim(); /* if the release name not contains the language we add it because it is know from category */ if (cat.Contains("hun") && !releasedata.Contains("hun")) { releasedata += ".hun"; } // release description contains [imdb: ****] but we only need the data before it for title String[] description = { release.Description, "" }; if (release.Description.Contains("[imdb:")) { description = release.Description.Split('['); description[1] = "[" + description[1]; } else { release.Title = (description[0].Trim() + "." + seasonep.Trim() + "." + releasedata.Trim('.')).Replace(' ', '.'); } // if search is done for S0X than we dont want to put . between S0X and E0X Match match = Regex.Match(releasedata, @"^E\d\d?"); if (seasonep.Length == 3 && match.Success) { release.Title = (description[0].Trim() + "." + seasonep.Trim() + releasedata.Trim('.')).Replace(' ', '.'); } // add back imdb points to the description [imdb: 8.7] release.Description = temp + " " + description[1]; release.Description = release.Description.Trim(); releases.Add(release); } } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); // replace any space, special char, etc. with % (wildcard) Regex ReplaceRegex = new Regex("[^a-zA-Z0-9]+"); searchString = ReplaceRegex.Replace(searchString, "%"); var searchUrl = SearchUrl; var queryCollection = new NameValueCollection(); queryCollection.Add("total", "146"); // Not sure what this is about but its required! var cat = "0"; var queryCats = MapTorznabCapsToTrackers(query); if (queryCats.Count == 1) { cat = queryCats.First().ToString(); } queryCollection.Add("cat", cat); queryCollection.Add("searchin", "filename"); queryCollection.Add("search", searchString); queryCollection.Add("page", "1"); searchUrl += "?" + queryCollection.GetQueryString(); var extraHeaders = new Dictionary <string, string>() { { "X-Requested-With", "XMLHttpRequest" } }; var response = await RequestStringWithCookiesAndRetry(searchUrl, null, SearchUrlReferer, extraHeaders); var results = response.Content; try { CQ dom = results; var rows = dom["tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); var qRow = row.Cq(); var qTitleLink = qRow.Find("td:eq(1) a:eq(0)").First(); release.Title = qTitleLink.Text().Trim(); // If we search an get no results, we still get a table just with no info. if (string.IsNullOrWhiteSpace(release.Title)) { break; } release.Guid = new Uri(qTitleLink.Attr("href")); release.Comments = release.Guid; var dateString = qRow.Find("td:eq(4)").Text(); release.PublishDate = DateTime.ParseExact(dateString, "dd MMM yy", CultureInfo.InvariantCulture); var qLink = qRow.Find("td:eq(2) a"); if (qLink.Length != 0) // newbie users don't see DL links { release.Link = new Uri(qLink.Attr("href")); } else { // use comments link as placeholder // null causes errors during export to torznab // skipping the release prevents newbie users from adding the tracker (empty result) release.Link = release.Comments; } var sizeStr = qRow.Find("td:eq(5)").Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); var connections = qRow.Find("td:eq(7)").Text().Trim().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); release.Seeders = ParseUtil.CoerceInt(connections[0].Trim()); release.Peers = ParseUtil.CoerceInt(connections[1].Trim()) + release.Seeders; release.Grabs = ParseUtil.CoerceLong(connections[2].Trim()); var rCat = row.Cq().Find("td:eq(0) a").First().Attr("href"); var rCatIdx = rCat.IndexOf("cat="); if (rCatIdx > -1) { rCat = rCat.Substring(rCatIdx + 4); } release.Category = MapTrackerCatToNewznab(rCat); if (qRow.Find("img[alt=\"Gold Torrent\"]").Length >= 1) { release.DownloadVolumeFactor = 0; } else if (qRow.Find("img[alt=\"Silver Torrent\"]").Length >= 1) { release.DownloadVolumeFactor = 0.5; } else { release.DownloadVolumeFactor = 1; } var ULFactorImg = qRow.Find("img[alt*=\"x Multiplier Torrent\"]"); if (ULFactorImg.Length >= 1) { release.UploadVolumeFactor = ParseUtil.CoerceDouble(ULFactorImg.Attr("alt").Split('x')[0]); } else { release.UploadVolumeFactor = 1; } qTitleLink.Remove(); release.Description = qRow.Find("td:eq(1)").Text(); releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var searchString = query.GetQueryString(); var keywordSearch = !string.IsNullOrWhiteSpace(searchString); var releases = new List <ReleaseInfo>(); var queryCollection = !keywordSearch ? new NameValueCollection { { "search_id", "active_topics" } } : new NameValueCollection { { "sr", "posts" }, //Search all posts { "ot", "1" }, //Search only in forums trackers (checked) { "keywords", searchString }, { "sf", "titleonly" } }; var searchUrl = SearchUrl + "?" + queryCollection.GetQueryString(); var results = await RequestStringWithCookies(searchUrl); if (!results.Content.Contains("ucp.php?mode=logout")) { await ApplyConfiguration(null); results = await RequestStringWithCookies(searchUrl); } try { var resultParser = new HtmlParser(); var searchResultDocument = resultParser.ParseDocument(results.Content); var rowSelector = keywordSearch ? "div.search div.postbody > h3 > a" : "ul.topics > li.row:has(i.fa-paperclip) a.topictitle"; // Torrent lines have paperclip icon. Chat topics don't var rows = searchResultDocument.QuerySelectorAll(rowSelector); foreach (var rowLink in rows) { var detailLink = SiteLink + rowLink.GetAttribute("href"); var detailsResult = await RequestStringWithCookies(detailLink); var detailsDocument = resultParser.ParseDocument(detailsResult.Content); var detailRow = detailsDocument.QuerySelector("table.table2 > tbody > tr"); if (detailRow == null) { continue; //No torrents in result } var qDownloadLink = detailRow.QuerySelector("a[href^=\"/download/torrent\"]"); var link = new Uri(SiteLink + qDownloadLink.GetAttribute("href").TrimStart('/')); var timestr = detailRow.Children[0].QuerySelector("ul.dropdown-contents span.my_tt").TextContent; var publishDate = DateTimeUtil.FromUnknown(timestr, "UK"); var forumId = detailsDocument.QuerySelector("li.breadcrumbs").LastElementChild .GetAttribute("data-forum-id"); var sizeString = detailRow.Children[4].QuerySelector("span.my_tt").GetAttribute("title"); var size = ParseUtil.CoerceLong(Regex.Replace(sizeString, @"[^0-9]", string.Empty)); var comments = new Uri(detailLink); var grabs = ParseUtil.CoerceInt(detailRow.Children[0].QuerySelector("span.complet").TextContent); var seeders = ParseUtil.CoerceInt(detailRow.Children[2].QuerySelector("span.seed").TextContent); var leechers = ParseUtil.CoerceInt(detailRow.Children[3].QuerySelector("span.leech").TextContent); var release = new ReleaseInfo { MinimumRatio = 1, MinimumSeedTime = 0, DownloadVolumeFactor = 1, UploadVolumeFactor = 1, Seeders = seeders, Grabs = grabs, Peers = leechers + seeders, Title = rowLink.TextContent, Comments = comments, Guid = comments, Link = link, PublishDate = publishDate, Category = MapTrackerCatToNewznab(forumId), Size = size, }; releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var pairs = new List <KeyValuePair <string, string> >(); pairs.Add(new KeyValuePair <string, string>("nyit_sorozat_resz", "true")); pairs.Add(new KeyValuePair <string, string>("miben", "name")); pairs.Add(new KeyValuePair <string, string>("tipus", "kivalasztottak_kozott")); pairs.Add(new KeyValuePair <string, string>("submit.x", "1")); pairs.Add(new KeyValuePair <string, string>("submit.y", "1")); pairs.Add(new KeyValuePair <string, string>("submit", "Ok")); pairs.Add(new KeyValuePair <string, string>("mire", searchString)); var cats = MapTorznabCapsToTrackers(query); if (cats.Count == 0) { cats = GetAllTrackerCategories(); } foreach (var lcat in LanguageCats) { if (!configData.Hungarian.Value) { cats.Remove(lcat + "_hun"); } if (!configData.English.Value) { cats.Remove(lcat); } } foreach (var cat in cats) { pairs.Add(new KeyValuePair <string, string>("kivalasztott_tipus[]", cat)); } var results = await PostDataWithCookiesAndRetry(SearchUrl, pairs); try { CQ dom = results.Content; ReleaseInfo release; var rows = dom[".box_torrent_all"].Find(".box_torrent"); foreach (var row in rows) { CQ qRow = row.Cq(); var key = dom ["link[rel=alternate]"].First().Attr("href").Split('=').Last(); release = new ReleaseInfo(); var torrentTxt = qRow.Find(".torrent_txt, .torrent_txt2").Find("a").Get(0); //if (torrentTxt == null) continue; release.Title = torrentTxt.GetAttribute("title"); release.Description = qRow.Find("div.siterank").Text(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; release.DownloadVolumeFactor = 0; release.UploadVolumeFactor = 1; string downloadLink = SiteLink + torrentTxt.GetAttribute("href"); string downloadId = downloadLink.Substring(downloadLink.IndexOf("&id=") + 4); release.Link = new Uri(SiteLink.ToString() + "torrents.php?action=download&id=" + downloadId + "&key=" + key); release.Comments = new Uri(SiteLink.ToString() + "torrents.php?action=details&id=" + downloadId); release.Guid = new Uri(release.Comments.ToString() + "#comments");; release.Seeders = ParseUtil.CoerceInt(qRow.Find(".box_s2").Find("a").First().Text()); release.Peers = ParseUtil.CoerceInt(qRow.Find(".box_l2").Find("a").First().Text()) + release.Seeders; var imdblink = qRow.Find("a[href*=\".imdb.com/title\"]").Attr("href"); release.Imdb = ParseUtil.GetLongFromString(imdblink); var banner = qRow.Find("img.infobar_ico").Attr("onmouseover"); if (banner != null) { Regex BannerRegEx = new Regex(@"mutat\('(.*?)', '", RegexOptions.Compiled); var BannerMatch = BannerRegEx.Match(banner); var bannerurl = BannerMatch.Groups[1].Value; release.BannerUrl = new Uri(bannerurl); } release.PublishDate = DateTime.Parse(qRow.Find(".box_feltoltve2").Get(0).InnerHTML.Replace("<br />", " "), CultureInfo.InvariantCulture); string[] sizeSplit = qRow.Find(".box_meret2").Get(0).InnerText.Split(' '); release.Size = ReleaseInfo.GetBytes(sizeSplit[1].ToLower(), ParseUtil.CoerceFloat(sizeSplit[0])); string catlink = qRow.Find("a:has(img[class='categ_link'])").First().Attr("href"); string cat = ParseUtil.GetArgumentFromQueryString(catlink, "tipus"); release.Category = MapTrackerCatToNewznab(cat); releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > ParseLast24HoursAsync() { var releases = new List <ReleaseInfo>(); var results = await RequestStringWithCookies(TodayUrl); if (results.IsRedirect) { // re-login await ApplyConfiguration(null); results = await RequestStringWithCookies(TodayUrl); } try { const string rowsSelector = "table.torrent_table > tbody > tr:not(tr.colhead)"; var searchResultParser = new HtmlParser(); var searchResultDocument = searchResultParser.ParseDocument(results.Content); var rows = searchResultDocument.QuerySelectorAll(rowsSelector); foreach (var row in rows) { try { var release = new ReleaseInfo { MinimumRatio = 1, MinimumSeedTime = 0 }; var qDetailsLink = row.QuerySelector("a.BJinfoBox"); var qBJinfoBox = qDetailsLink.QuerySelector("span"); var qCatLink = row.QuerySelector("a[href^=\"/torrents.php?filter_cat\"]"); var qDlLink = row.QuerySelector("a[href^=\"torrents.php?action=download\"]"); var qSeeders = row.QuerySelector("td:nth-child(4)"); var qLeechers = row.QuerySelector("td:nth-child(5)"); var qQuality = row.QuerySelector("font[color=\"red\"]"); var qFreeLeech = row.QuerySelector("font[color=\"green\"]:contains(Free)"); var qTitle = qDetailsLink.QuerySelector("font"); // Get international title if available, or use the full title if not release.Title = Regex.Replace(qTitle.TextContent, @".* \[(.*?)\](.*)", "$1$2"); var year = ""; release.Description = ""; var extraInfo = ""; foreach (var child in qBJinfoBox.ChildNodes) { var type = child.NodeType; if (type != NodeType.Text) { continue; } var line = child.TextContent; if (line.StartsWith("Tamanho:")) { var size = line.Substring("Tamanho: ".Length); release.Size = ReleaseInfo.GetBytes(size); } else if (line.StartsWith("Lançado em: ")) { var publishDateStr = line.Substring("Lançado em: ".Length).Replace("às ", ""); publishDateStr += " +0"; var publishDate = DateTime.SpecifyKind( DateTime.ParseExact(publishDateStr, "dd/MM/yyyy HH:mm z", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); release.PublishDate = publishDate.ToLocalTime(); } else if (line.StartsWith("Ano:")) { year = line.Substring("Ano: ".Length); } else { release.Description += line + "\n"; if (line.Contains(":")) { if (!(line.StartsWith("Lançado") || line.StartsWith("Resolução") || line.StartsWith("Idioma") || line.StartsWith("Autor"))) { var info = line.Substring(line.IndexOf(": ", StringComparison.Ordinal) + 2); if (info == "Dual Áudio") { info = "Dual"; } extraInfo += info + " "; } } } } var catStr = qCatLink.GetAttribute("href").Split('=')[1].Split('&')[0]; release.Title = FixAbsoluteNumbering(release.Title); if (!string.IsNullOrEmpty(year)) { release.Title += " " + year; } if (qQuality != null) { var quality = qQuality.TextContent; release.Title += quality switch { "4K" => " 2160p", "Full HD" => " 1080p", "HD" => " 720p", _ => " 480p" }; } release.Title += " " + extraInfo.TrimEnd(); release.Category = MapTrackerCatToNewznab(catStr); release.Link = new Uri(SiteLink + qDlLink.GetAttribute("href")); release.Comments = new Uri(SiteLink + qDetailsLink.GetAttribute("href")); release.Guid = release.Link; release.Seeders = ParseUtil.CoerceInt(qSeeders.TextContent); release.Peers = ParseUtil.CoerceInt(qLeechers.TextContent) + release.Seeders; release.DownloadVolumeFactor = qFreeLeech != null ? 0 : 1; release.UploadVolumeFactor = 1; releases.Add(release); } catch (Exception ex) { logger.Error($"{ID}: Error while parsing row '{row.OuterHtml}': {ex.Message}"); } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = BrowseUrl; var queryCollection = new NameValueCollection(); if (!string.IsNullOrWhiteSpace(query.ImdbID)) { queryCollection.Add("q", query.ImdbID); } else if (!string.IsNullOrWhiteSpace(searchString)) { queryCollection.Add("q", searchString); } foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add(cat, string.Empty); } if (queryCollection.Count > 0) { searchUrl += "?" + queryCollection.GetQueryString(); } var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl); var results = response.Content; try { CQ dom = results; var rows = dom["table[id='torrents'] > tbody > tr"]; foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); var qRow = row.Cq(); var qTitleLink = qRow.Find("a[href^=\"/details.php?id=\"]").First(); release.Title = qTitleLink.Text().Trim(); // If we search an get no results, we still get a table just with no info. if (string.IsNullOrWhiteSpace(release.Title)) { break; } release.Guid = new Uri(SiteLink + qTitleLink.Attr("href").Substring(1)); release.Comments = release.Guid; var descString = qRow.Find(".t_ctime").Text(); var dateString = descString.Split('|').Last().Trim(); dateString = dateString.Split(new string[] { " by " }, StringSplitOptions.None)[0]; release.PublishDate = DateTimeUtil.FromTimeAgo(dateString); var qLink = row.ChildElements.ElementAt(3).Cq().Children("a"); release.Link = new Uri(SiteLink + WebUtility.UrlEncode(qLink.Attr("href").TrimStart('/'))); var sizeStr = row.ChildElements.ElementAt(5).Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(qRow.Find(".t_seeders").Text().Trim()); release.Peers = ParseUtil.CoerceInt(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders; var catIcon = row.Cq().Find("td:eq(0) a"); if (catIcon.Length >= 1) // Torrents - Category column == Icons { release.Category = MapTrackerCatToNewznab(catIcon.First().Attr("href").Substring(1)); } else // Torrents - Category column == Text (Code is not supported) { release.Category = MapTrackerCatDescToNewznab(row.Cq().Find("td:eq(0)").Text()); } var filesElement = row.Cq().Find("a[href*=\"/files\"]"); // optional if (filesElement.Length == 1) { release.Files = ParseUtil.CoerceLong(filesElement.Text()); } var grabs = row.Cq().Find("td:nth-last-child(3)").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); if (row.Cq().Find("span.t_tag_free_leech").Any()) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(results, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { List <ReleaseInfo> releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = SearchUrl; var queryCollection = new NameValueCollection(); queryCollection.Add("incldead", "1"); queryCollection.Add("rel_type", "0"); // Alle if (query.ImdbID != null) { queryCollection.Add("searchin", "imdb"); queryCollection.Add("search", query.ImdbID); } else { queryCollection.Add("searchin", "title"); if (!string.IsNullOrWhiteSpace(searchString)) { // use AND+wildcard operator to avoid getting to many useless results var searchStringArray = Regex.Split(searchString.Trim(), "[ _.-]+", RegexOptions.Compiled).ToList(); searchStringArray = searchStringArray.Where(x => x.Length >= 3).ToList(); // remove words with less than 3 characters searchStringArray = searchStringArray.Where(x => !new string[] { "der", "die", "das", "the" }.Contains(x.ToLower())).ToList(); // remove words with less than 3 characters searchStringArray = searchStringArray.Select(x => "+" + x + "*").ToList(); // add AND operators+wildcards var searchStringFinal = String.Join(" ", searchStringArray); queryCollection.Add("search", searchStringFinal); } } foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add("c" + cat, "1"); } searchUrl += "?" + queryCollection.GetQueryString(); var results = await RequestStringWithCookiesAndRetry(searchUrl); if (results.IsRedirect) { await ApplyConfiguration(null); results = await RequestStringWithCookiesAndRetry(searchUrl); } try { CQ dom = results.Content; var rows = dom["table.torrent_table > tbody > tr"]; var globalFreeleech = dom.Find("legend:contains(\"Freeleech\")+ul > li > b:contains(\"Freeleech\")").Any(); foreach (var row in rows.Skip(1)) { var release = new ReleaseInfo(); release.MinimumRatio = 1; release.MinimumSeedTime = 96 * 60 * 60; var qRow = row.Cq(); var catStr = row.ChildElements.ElementAt(0).FirstElementChild.GetAttribute("href").Split('=')[1]; release.Category = MapTrackerCatToNewznab(catStr); var qLink = row.ChildElements.ElementAt(2).FirstElementChild.Cq(); release.Link = new Uri(SiteLink + qLink.Attr("href")); var torrentId = qLink.Attr("href").Split('=').Last(); var descCol = row.ChildElements.ElementAt(1); var qCommentLink = descCol.FirstElementChild.Cq(); var torrentTag = descCol.Cq().Find("span.torrent-tag"); var torrentTags = torrentTag.Elements.Select(x => x.InnerHTML).ToList(); release.Title = qCommentLink.Attr("title"); release.Description = String.Join(", ", torrentTags); release.Comments = new Uri(SiteLink + qCommentLink.Attr("href").Replace("&hit=1", "")); release.Guid = release.Comments; var torrent_details = descCol.Cq().Find(".torrent_details").Get(0); var rawDateStr = torrent_details.ChildNodes.ElementAt(torrent_details.ChildNodes.Length - 3).Cq().Text(); var dateStr = rawDateStr.Trim().Replace("von", "").Trim(); DateTime dateGerman; if (dateStr.StartsWith("Heute ")) { dateGerman = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateStr.Split(' ')[1]); } else if (dateStr.StartsWith("Gestern ")) { dateGerman = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Unspecified) + TimeSpan.Parse(dateStr.Split(' ')[1]) - TimeSpan.FromDays(1); } else { dateGerman = DateTime.SpecifyKind(DateTime.ParseExact(dateStr, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture), DateTimeKind.Unspecified); } DateTime pubDateUtc = TimeZoneInfo.ConvertTimeToUtc(dateGerman, germanyTz); release.PublishDate = pubDateUtc.ToLocalTime(); var imdbLink = descCol.Cq().Find("a[href*=\"&searchin=imdb\"]"); if (imdbLink.Any()) { release.Imdb = ParseUtil.GetLongFromString(imdbLink.Attr("href")); } var sizeStr = row.ChildElements.ElementAt(5).Cq().Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(row.ChildElements.ElementAt(7).Cq().Text()); release.Peers = ParseUtil.CoerceInt(row.ChildElements.ElementAt(8).Cq().Text()) + release.Seeders; var grabs = qRow.Find("td:nth-child(7)").Text(); release.Grabs = ParseUtil.CoerceInt(grabs); if (globalFreeleech) { release.DownloadVolumeFactor = 0; } else if (qRow.Find("span.torrent-tag-free").Length >= 1) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } release.UploadVolumeFactor = 1; releases.Add(release); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
/// <summary> /// Execute our search query /// </summary> /// <param name="query">Query</param> /// <returns>Releases</returns> public async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var torrentRowList = new List <CQ>(); var searchTerm = query.GetQueryString(); var searchUrl = SearchUrl; int nbResults = 0; int pageLinkCount = 0; // 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.Where(i => i.Query == searchTerm).FirstOrDefault(); if (cachedResult != null) { return(cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray()); } } } // Build our query var request = buildQuery(searchTerm, query, searchUrl); // Getting results & Store content WebClientStringResult results = await queryExec(request); fDom = results.Content; try { // Find torrent rows var firstPageRows = findTorrentRows(); // Add them to torrents list torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq())); // Check if there are pagination links at bottom Boolean pagination = (fDom[".pager_align > a"].Length != 0); // If pagination available if (pagination) { // Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page) pageLinkCount = ParseUtil.CoerceInt(Regex.Match(fDom[".pager_align > a:not(:last-child)"].Last().Attr("href").ToString(), @"\d+").Value) + 1; // Calculate average number of results (based on torrents rows lenght on first page) nbResults = firstPageRows.Count() * pageLinkCount; } else { // Check if we have a minimum of one result if (firstPageRows.Length >= 1) { // Retrieve total count on our alone page nbResults = firstPageRows.Count(); pageLinkCount = 1; } else { output("\nNo result found for your query, please try another search term ...\n", "info"); // No result found for this query return(releases); } } output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !"); output("\nThere are " + firstPageRows.Length + " results on the first page !"); // If we have a term used for search and pagination result superior to one if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1) { // Starting with page #2 for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++) { output("\nProcessing page #" + i); // Request our page latencyNow(); // Build our query var pageRequest = buildQuery(searchTerm, query, searchUrl, (i - 1)); // Getting results & Store content WebClientStringResult pageResults = await queryExec(pageRequest); // Assign response fDom = pageResults.Content; // Process page results var additionalPageRows = findTorrentRows(); // Add them to torrents list torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq())); } } else { // No search term, maybe testing... so registring passkey for future uses string infosData = firstPageRows.First().Find("td:eq(2) > a").Attr("href"); IList <string> infosList = infosData.Split('&').Select(s => s.Trim()).Where(s => s != String.Empty).ToList(); IList <string> infosTracker = infosList.Select(s => s.Split(new[] { '=' }, 2)[1].Trim()).ToList(); output("\nStoring Passkey for future uses... \"" + infosTracker[2] + "\""); ConfigData.PassKey.Value = infosTracker[2]; } // Loop on results foreach (CQ tRow in torrentRowList) { output("\n=>> Torrent #" + (releases.Count + 1)); // ID string row = tRow.Html().ToString(); int id = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1) > a").Attr("href").ToString(), @"\d+").Value); output("ID: " + id); // Release Name string name = tRow.Find("td:eq(1) > a").Attr("title").ToString(); output("Release: " + name); // Category string infosDataCategory = firstPageRows.First().Find("td:eq(0) > a").Attr("href"); IList <string> infosListCategory = infosDataCategory.Split('&').Select(s => s.Trim()).Where(s => s != String.Empty).ToList(); IList <string> infosCategory = infosListCategory.Select(s => s.Split(new[] { '=' }, 2)[0].Trim()).ToList(); string categoryID = infosCategory.Last().TrimStart('c'); output("Category: " + MapTrackerCatToNewznab(categoryID) + " (" + categoryID + ")"); // Seeders int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(6)").Text(), @"\d+").Value); output("Seeders: " + seeders); // Leechers int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(7)").Text(), @"\d+").Value); output("Leechers: " + leechers); // Completed int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5)").Text(), @"\d+").Value); output("Completed: " + completed); // Size string sizeStr = tRow.Find("td:eq(4)").Text().Trim().Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb"); long size = ReleaseInfo.GetBytes(sizeStr); output("Size: " + sizeStr + " (" + size + " bytes)"); // Health int percent = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(8) > img").Attr("alt").ToString(), @"\d+").Value); output("Health: " + percent + "%"); // Publish DateToString //var date = agoToDate(null); int timestamp = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1)").Attr("data-added").ToString(), @"\d+").Value); DateTime date = unixTimeStampToDateTime(timestamp); output("Released on: " + date.ToLocalTime() + " (TS >> " + timestamp + ")"); // Torrent Details URL Uri detailsLink = new Uri(TorrentDescriptionUrl + id); output("Details: " + detailsLink.AbsoluteUri); // Torrent Comments URL Uri commentsLink = new Uri(TorrentCommentUrl + id); output("Comments Link: " + commentsLink.AbsoluteUri); // Torrent Download URL Uri downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()).Replace("{passkey}", ConfigData.PassKey.Value)); output("Download Link: " + downloadLink.AbsoluteUri); // Building release infos var release = new ReleaseInfo(); release.Category = MapTrackerCatToNewznab(categoryID.ToString()); release.Title = name; release.Seeders = seeders; release.Peers = seeders + leechers; release.MinimumRatio = 1; release.MinimumSeedTime = 345600; release.PublishDate = date; release.Size = size; release.Guid = detailsLink; release.Comments = commentsLink; release.Link = downloadLink; releases.Add(release); } } catch (Exception ex) { OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex); } // Return found releases return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); 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(); var dom = parser.ParseDocument(results.ContentString); var rows = dom.QuerySelectorAll("#torrents-table > tbody > tr"); foreach (var row in rows.Skip(1)) { var qComments = row.QuerySelector(".br_right > a"); var comments = new Uri(SiteLink + qComments.GetAttribute("href")); var title = qComments.QuerySelector("b").TextContent; 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 = ReleaseInfo.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 { Comments = comments, Guid = comments, 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); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var searchString = query.GetQueryString(); var searchUrl = SearchUrl; if (!string.IsNullOrWhiteSpace(searchString)) { searchUrl += "&search=" + WebUtilityHelpers.UrlEncode(searchString, Encoding); } string.Format(SearchUrl, WebUtilityHelpers.UrlEncode(searchString, Encoding)); var cats = MapTorznabCapsToTrackers(query); if (cats.Count > 0) { foreach (var cat in cats) { searchUrl += "&c" + cat + "=1"; } } var response = await RequestStringWithCookies(searchUrl); try { CQ dom = response.Content; CQ qRows = dom[".browse > div > div"]; foreach (var row in qRows) { var release = new ReleaseInfo(); var qRow = row.Cq(); var debug = qRow.Html(); release.MinimumRatio = 1; release.MinimumSeedTime = 172800; var titleParts = qRow.Find(".bTitle").Text().Split('/'); if (titleParts.Length >= 2) { release.Title = titleParts[1].Trim(); } else { release.Title = titleParts[0].Trim(); } var qDetailsLink = qRow.Find("a[title][href^=\"details.php\"]"); release.Comments = new Uri(SiteLink + qDetailsLink.Attr("href")); release.Link = new Uri(SiteLink + qRow.Find("a[href^=\"download.php\"]").Attr("href")); release.Guid = release.Link; var dateString = qRow.Find("div:last-child").Text().Trim(); var pattern = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"; var match = Regex.Match(dateString, pattern); if (match.Success) { release.PublishDate = DateTime.ParseExact(match.Value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); } var sizeStr = qRow.Find(".bSize").Text(); release.Size = ReleaseInfo.GetBytes(sizeStr); release.Seeders = ParseUtil.CoerceInt(qRow.Find(".bUping").Text().Trim()); release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find(".bDowning").Text().Trim()); var files = qRow.Find("div.bFiles").Get(0).LastChild.ToString(); release.Files = ParseUtil.CoerceInt(files); var grabs = qRow.Find("div.bFinish").Get(0).LastChild.ToString(); release.Grabs = ParseUtil.CoerceInt(grabs); if (qRow.Find("img[src=\"/pic/free.jpg\"]").Length >= 1) { release.DownloadVolumeFactor = 0; } else { release.DownloadVolumeFactor = 1; } if (qRow.Find("img[src=\"/pic/triple.jpg\"]").Length >= 1) { release.UploadVolumeFactor = 3; } else if (qRow.Find("img[src=\"/pic/double.jpg\"]").Length >= 1) { release.UploadVolumeFactor = 2; } else { release.UploadVolumeFactor = 1; } releases.Add(release); } } catch (Exception ex) { OnParseError(response.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); var qc = new List <KeyValuePair <string, string> > // NameValueCollection don't support cat[]=19&cat[]=6 { { "st", "1" } // search in title }; if (query.IsImdbQuery) { qc.Add("search", query.ImdbID); qc.Add("sd", "1"); // search in description } else { qc.Add("search", query.GetQueryString()); } // parse categories and tags var catGroups = new HashSet <string>(); // HashSet instead of List to avoid duplicates var tagGroups = new HashSet <string>(); var cats = MapTorznabCapsToTrackers(query); foreach (var cat in cats) { // "cat[]=7&tags=x264" var cSplit = cat.Split('&'); var gSplit = cSplit[0].Split('='); if (gSplit.Length > 1) { catGroups.Add(gSplit[1]); // category = 7 } if (cSplit.Length > 1) { var tSplit = cSplit[1].Split('='); if (tSplit.Length > 1) { tagGroups.Add(tSplit[1]); // tag = x264 } } } // add categories foreach (var cat in catGroups) { qc.Add("cat[]", cat); } // do not include too many tags as it'll mess with their servers if (tagGroups.Count < 7) { qc.Add("tags", string.Join(",", tagGroups)); // if tags are specified match any // if no tags are specified match all, with any we get random results qc.Add("tf", tagGroups.Any() ? "any" : "all"); } var searchUrl = SearchUrl + "?" + qc.GetQueryString(); var response = await RequestStringWithCookiesAndRetry(searchUrl); if (response.IsRedirect) // re-login { await ApplyConfiguration(null); response = await RequestStringWithCookiesAndRetry(searchUrl); } try { var parser = new HtmlParser(); var dom = parser.ParseDocument(response.Content); var rows = dom.QuerySelectorAll("table > tbody > tr.browse"); foreach (var row in rows) { var qLink = row.Children[1].QuerySelector("a"); var title = qLink.GetAttribute("title"); if (qLink.QuerySelectorAll("span").Length == 1 && title.StartsWith("NEW! |")) { title = title.Substring(6).Trim(); } if (!query.MatchQueryStringAND(title)) { continue; // we have to skip bad titles due to tags + any word search } var comments = new Uri(SiteLink + qLink.GetAttribute("href")); var link = new Uri(SiteLink + row.Children[2].QuerySelector("a").GetAttribute("href")); var dateStr = Regex.Replace(row.Children[5].InnerHtml, @"\<br[\s]{0,1}[\/]{0,1}\>", " "); var publishDate = DateTimeUtil.FromTimeAgo(dateStr); var files = ParseUtil.CoerceInt(row.Children[3].TextContent); var size = ReleaseInfo.GetBytes(row.Children[7].TextContent); var grabs = ParseUtil.CoerceInt(row.Children[8].TextContent); var seeders = ParseUtil.CoerceInt(row.Children[9].TextContent); var leechers = ParseUtil.CoerceInt(row.Children[10].TextContent); var cat = row.FirstElementChild.FirstElementChild.GetAttribute("href").Replace("browse.php?", string.Empty); var release = new ReleaseInfo { Title = title, Comments = comments, Guid = comments, Link = link, PublishDate = publishDate, Size = size, Category = MapTrackerCatToNewznab(cat), Files = files, Grabs = grabs, Seeders = seeders, Peers = leechers + seeders, MinimumRatio = 1, MinimumSeedTime = 172800, // 48 hours DownloadVolumeFactor = 0, // ratioless UploadVolumeFactor = 1 }; releases.Add(release); } } catch (Exception ex) { OnParseError(response.Content, ex); } return(releases); }
protected override async Task <IEnumerable <ReleaseInfo> > PerformQuery(TorznabQuery query) { var releases = new List <ReleaseInfo>(); // TODO: IMDB search is available but it requires to parse the details page var qc = new NameValueCollection { { "order_by", "time" }, { "order_way", "desc" }, { "action", "basic" }, { "searchsubmit", "1" }, { "searchstr", query.IsImdbQuery ? query.ImdbID : query.GetQueryString() } }; var catList = MapTorznabCapsToTrackers(query); foreach (var cat in catList) { qc.Add($"filter_cat[{cat}]", "1"); } var searchUrl = BrowseUrl + "?" + qc.GetQueryString(); var results = await RequestWithCookiesAsync(searchUrl); try { var parser = new HtmlParser(); var doc = parser.ParseDocument(results.ContentString); var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent"); foreach (var row in rows) { var qDetailsLink = row.QuerySelector("a.torrent_name"); var year = qDetailsLink.NextSibling.TextContent.Replace("[", "").Replace("]", "").Trim(); var tags = row.QuerySelector("div.torrent_info").FirstChild.TextContent.Replace(" / ", " ").Trim(); var title = $"{qDetailsLink.TextContent} {year} {tags}"; var description = row.QuerySelector("div.tags").TextContent.Trim(); var details = new Uri(SiteLink + qDetailsLink.GetAttribute("href")); var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last(); var link = new Uri(SiteLink + "torrents.php?action=download&id=" + torrentId); var posterStr = qDetailsLink.GetAttribute("data-cover"); var poster = !string.IsNullOrWhiteSpace(posterStr) ? new Uri(qDetailsLink.GetAttribute("data-cover")) : null; var files = ParseUtil.CoerceLong(row.QuerySelector("td:nth-child(3)").TextContent); var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent); var size = ReleaseInfo.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent); var grabs = ParseUtil.CoerceLong(row.QuerySelector("td:nth-child(6)").TextContent); var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent); var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent); var dlVolumeFactor = row.QuerySelector("strong.tl_free") != null ? 0 : 1; var cat = row.QuerySelector("td.cats_col > div").GetAttribute("class").Replace("tooltip cats_", ""); var category = new List <int> { cat switch { "featurefilm" => TorznabCatType.Movies.ID, "shortfilm" => TorznabCatType.Movies.ID, "miniseries" => TorznabCatType.TV.ID, "other" => TorznabCatType.Other.ID, _ => throw new Exception($"Unknown category: {cat}") } }; // TODO: TMDb is also available var qImdb = row.QuerySelector("a[href^=\"https://www.imdb.com\"]"); var imdb = qImdb != null?ParseUtil.GetImdbID(qImdb.GetAttribute("href").Split('/').Last()) : null; var release = new ReleaseInfo { MinimumRatio = 1, MinimumSeedTime = 259200, Description = description, Title = title, PublishDate = publishDate, Category = category, Link = link, Details = details, Guid = link, Imdb = imdb, Poster = poster, Seeders = seeders, Peers = leechers + seeders, Size = size, Grabs = grabs, Files = files, DownloadVolumeFactor = dlVolumeFactor, UploadVolumeFactor = 1 }; releases.Add(release); } } catch (Exception ex) { OnParseError(results.ContentString, ex); } return(releases); }