private async Task <List <ReleaseInfo> > FetchEpisodeReleases(string url) { logger.Info("FetchEpisodeReleases: " + url); var results = await RequestStringAndRelogin(url); var releases = new List <ReleaseInfo>(); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var playButton = document.QuerySelector("div.external-btn"); if (playButton != null) { var urlDetails = new TrackerUrlDetails(playButton); releases = await FetchTrackerReleases(urlDetails); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > FetchEpisodeReleases(string url) { logger.Debug("FetchEpisodeReleases: " + url); var results = await RequestStringAndRelogin(url); var releases = new List <ReleaseInfo>(); try { var parser = new HtmlParser(); var document = parser.ParseDocument(results.Content); var playButton = document.QuerySelector("div.external-btn"); if (playButton != null && !playButton.ClassList.Contains("inactive")) { var comments = new Uri(url); var dateString = document.QuerySelector("div.title-block > div.details-pane > div.left-box").TextContent; dateString = TrimString(dateString, "eng: ", " г."); // '... Дата выхода eng: 09 марта 2012 г. ...' -> '09 марта 2012' DateTime date; if (dateString.Length == 4) //dateString might be just a year, e.g. https://www.lostfilm.tv/series/Ghosted/season_1/episode_14/ { date = DateTime.ParseExact(dateString, "yyyy", CultureInfo.InvariantCulture).ToLocalTime(); } else { date = DateTime.Parse(dateString, new CultureInfo(Language)); // dd mmmm yyyy } var urlDetails = new TrackerUrlDetails(playButton); var episodeReleases = await FetchTrackerReleases(urlDetails); foreach (var release in episodeReleases) { release.Comments = comments; release.PublishDate = date; } releases.AddRange(episodeReleases); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > FetchTrackerReleases(TrackerUrlDetails details) { var queryCollection = new NameValueCollection(); queryCollection.Add("c", details.seriesId); queryCollection.Add("s", details.season); queryCollection.Add("e", string.IsNullOrEmpty(details.episode) ? "999" : details.episode); // 999 is a synonym for the whole serie var url = ReleaseUrl + "?" + queryCollection.GetQueryString(); logger.Debug("FetchTrackerReleases: " + url); // Get redirection page with generated link on it. This link can't be constructed manually as it contains Hash field and hashing algo is unknown. var results = await RequestStringWithCookies(url); if (results.Content == null) { throw new ExceptionWithConfigData("Empty response from " + url, configData); } if (results.Content == "log in first") { throw new ExceptionWithConfigData(results.Content, configData); } try { var parser = new HtmlParser(); var document = parser.ParseDocument(results.Content); var meta = document.QuerySelector("meta"); var metaContent = meta.GetAttribute("content"); // Follow redirection defined by async url.replace var redirectionUrl = metaContent.Substring(metaContent.IndexOf("http")); return(await FollowTrackerRedirection(redirectionUrl, details)); } catch (Exception ex) { OnParseError(results.Content, ex); } // Failure path return(new List <ReleaseInfo>()); }
private async Task <List <ReleaseInfo> > FetchEpisodeReleases(string url) { logger.Debug("FetchEpisodeReleases: " + url); var results = await RequestStringAndRelogin(url); var releases = new List <ReleaseInfo>(); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var playButton = document.QuerySelector("div.external-btn"); if (playButton != null && !playButton.ClassList.Contains("inactive")) { var comments = new Uri(url); var dateString = document.QuerySelector("div.title-block > div.details-pane > div.left-box").TextContent; dateString = TrimString(dateString, "eng: ", " г."); // '... Дата выхода eng: 09 марта 2012 г. ...' -> '09 марта 2012' var date = DateTime.Parse(dateString, new CultureInfo(Language)); // dd mmmm yyyy var urlDetails = new TrackerUrlDetails(playButton); var episodeReleases = await FetchTrackerReleases(urlDetails); foreach (var release in episodeReleases) { release.Comments = comments; release.PublishDate = date; } releases.AddRange(episodeReleases); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > FollowTrackerRedirection(string url, TrackerUrlDetails details) { logger.Debug("FollowTrackerRedirection: " + url); var results = await RequestStringWithCookies(url); var releases = new List <ReleaseInfo>(); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var rows = document.QuerySelectorAll("div.inner-box--item"); logger.Debug("> Parsing " + rows.Count().ToString() + " releases"); var serieTitle = document.QuerySelector("div.inner-box--subtitle").TextContent; serieTitle = serieTitle.Substring(0, serieTitle.LastIndexOf(',')); var episodeInfo = document.QuerySelector("div.inner-box--text").TextContent; var episodeName = TrimString(episodeInfo, '(', ')'); foreach (var row in rows) { try { var release = new ReleaseInfo(); release.Category = new int[] { TorznabCatType.TV.ID }; var detailsInfo = row.QuerySelector("div.inner-box--desc").TextContent; var releaseDetails = parseReleaseDetailsRegex.Match(detailsInfo); if (releaseDetails == null) { throw new FormatException("Failed to map release details string: " + detailsInfo); } /* * For supported qualities see: * - TvCategoryParser.cs * - https://github.com/SickRage/SickRage/wiki/Quality-Settings#quality-names-to-recognize-the-quality-of-a-file */ var quality = releaseDetails.Groups["quality"].Value.Trim(); // Adapt shitty quality format for common algorythms quality = Regex.Replace(quality, "-Rip", "Rip", RegexOptions.IgnoreCase); quality = Regex.Replace(quality, "WEB-DLRip", "WEBDL", RegexOptions.IgnoreCase); quality = Regex.Replace(quality, "WEB-DL", "WEBDL", RegexOptions.IgnoreCase); quality = Regex.Replace(quality, "HDTVRip", "HDTV", RegexOptions.IgnoreCase); // Fix forgotten p-Progressive suffix in resolution index quality = Regex.Replace(quality, "1080 ", "1080p ", RegexOptions.IgnoreCase); quality = Regex.Replace(quality, "720 ", "720p ", RegexOptions.IgnoreCase); var techComponents = new string[] { "rus", quality }; var techInfo = string.Join(" ", techComponents.Where(s => !string.IsNullOrEmpty(s))); // Ru title: downloadLink.TextContent.Replace("\n", ""); // En title should be manually constructed. var titleComponents = new string[] { serieTitle, details.GetEpisodeString(), episodeName, techInfo }; release.Title = string.Join(" - ", titleComponents.Where(s => !string.IsNullOrEmpty(s))); var downloadLink = row.QuerySelector("div.inner-box--link > a"); release.Link = new Uri(downloadLink.GetAttribute("href")); release.Guid = release.Link; var sizeString = releaseDetails.Groups["size"].Value.ToUpper(); sizeString = sizeString.Replace("ТБ", "TB"); // untested sizeString = sizeString.Replace("ГБ", "GB"); sizeString = sizeString.Replace("МБ", "MB"); sizeString = sizeString.Replace("КБ", "KB"); // untested release.Size = ReleaseInfo.GetBytes(sizeString); logger.Debug("> Add: " + release.Title); releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("{0}: Error while parsing row '{1}':\n\n{2}", ID, row.OuterHtml, ex)); } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > FetchSeriesReleases(string url, TorznabQuery query, string filter) { logger.Debug("FetchSeriesReleases: " + url + " S: " + query.Season.ToString() + " E: " + query.Episode + " Filter: " + filter); var releases = new List <ReleaseInfo>(); var results = await RequestStringWithCookies(url); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var seasons = document.QuerySelectorAll("div.serie-block"); var rowSelector = "table.movie-parts-list > tbody > tr"; foreach (var season in seasons) { // Could ne null if serie-block is for Extras var seasonButton = season.QuerySelector("div.movie-details-block > div.external-btn"); // Process only season we're searching for if (seasonButton != null && query.Season > 0) { // If seasonButton in "inactive" it will not contain "onClick" handler. Better to parse element which always exists. var watchedButton = season.QuerySelector("div.movie-details-block > div.haveseen-btn"); var buttonCode = watchedButton.GetAttribute("data-code"); var currentSeason = buttonCode.Substring(buttonCode.IndexOf('-') + 1); if (currentSeason != query.Season.ToString()) { continue; // Can't match season by regex OR season not matches to a searched one } // Stop parsing season episodes if season pack was required but it's not available yet. if (seasonButton.ClassList.Contains("inactive")) { logger.Debug("> No season pack is found for S" + query.Season.ToString()); break; } } // Fetch season pack releases if no episode filtering is required. // If seasonButton implements "inactive" class there are no season pack available and each episode should be fetched separately. if (string.IsNullOrEmpty(query.Episode) && string.IsNullOrEmpty(filter) && seasonButton != null && !seasonButton.ClassList.Contains("inactive")) { var lastEpisode = season.QuerySelector(rowSelector); var dateColumn = lastEpisode.QuerySelector("td.delta"); var date = DateFromEpisodeColumn(dateColumn); var comments = new Uri(url); // Current season(-s) page url var urlDetails = new TrackerUrlDetails(seasonButton); var seasonReleases = await FetchTrackerReleases(urlDetails); foreach (var release in seasonReleases) { release.Comments = comments; release.PublishDate = date; } releases.AddRange(seasonReleases); if (query.Season > 0) { break; // Searched season was processed } // Skip parsing separate episodes if season pack was added if (seasonReleases.Count() > 0) { continue; } } // No season filtering was applied OR season pack in not available var rows = season.QuerySelectorAll(rowSelector).Where(s => !s.ClassList.Contains("not-available")); foreach (var row in rows) { var couldBreak = false; // Set to `true` if searched episode was found try { if (!string.IsNullOrEmpty(filter)) { var titles = row.QuerySelector("td.gamma > div"); if (titles.TextContent.IndexOf(filter, StringComparison.OrdinalIgnoreCase) == -1) { continue; } } var playButton = row.QuerySelector("td.zeta > div.external-btn"); if (!string.IsNullOrEmpty(query.Episode)) { var match = parsePlayEpisodeRegex.Match(playButton.GetAttribute("onclick")); var episode = match.Groups["episode"]; if (episode == null || episode.Value != query.Episode) { continue; } couldBreak = true; } var dateColumn = row.QuerySelector("td.delta"); // Contains both Date and EpisodeURL var date = DateFromEpisodeColumn(dateColumn); var link = dateColumn.GetAttribute("onclick"); // goTo('/series/Prison_Break/season_5/episode_9/',false) link = TrimString(link, '\'', '\''); var episodeUrl = SiteLink + link.TrimStart('/'); var comments = new Uri(episodeUrl); var urlDetails = new TrackerUrlDetails(playButton); var episodeReleases = await FetchTrackerReleases(urlDetails); foreach (var release in episodeReleases) { release.Comments = comments; release.PublishDate = date; } releases.AddRange(episodeReleases); } catch (Exception ex) { logger.Error(string.Format("{0}: Error while parsing row '{1}':\n\n{2}", ID, row.OuterHtml, ex)); } if (couldBreak) { break; } } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
private async Task <List <ReleaseInfo> > FollowTrackerRedirection(string url, TrackerUrlDetails details) { logger.Info("FollowTrackerRedirection: " + url); var results = await RequestStringWithCookies(url); var releases = new List <ReleaseInfo>(); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var rows = document.QuerySelectorAll("div.inner-box--item"); logger.Info("> Parsing " + rows.Count().ToString() + " releases"); var serieTitle = document.QuerySelector("div.inner-box--subtitle").TextContent; serieTitle = serieTitle.Substring(0, serieTitle.LastIndexOf(',')); var episodeInfo = document.QuerySelector("div.inner-box--text").TextContent; var episodeName = TrimString(episodeInfo, '(', ')'); foreach (var row in rows) { try { var release = new ReleaseInfo(); release.Category = new int[] { TorznabCatType.TV.ID }; var detailsInfo = row.QuerySelector("div.inner-box--desc").TextContent; var releaseDetails = parseReleaseDetailsRegex.Match(detailsInfo); if (releaseDetails == null) { throw new FormatException("Failed to map release details string: " + detailsInfo); } // Ru title: downloadLink.TextContent.Replace("\n", ""); // En title should be manually constructed. var titleComponents = new string[] { serieTitle, details.GetEpisodeString(), episodeName, releaseDetails.Groups["quality"].Value }; release.Title = string.Join(" - ", titleComponents.Where(s => !string.IsNullOrEmpty(s))); var downloadLink = row.QuerySelector("div.inner-box--link > a"); release.Link = new Uri(downloadLink.GetAttribute("href")); release.Guid = release.Link; var sizeString = releaseDetails.Groups["size"].Value; sizeString = sizeString.Replace("ТБ", "TB"); // untested sizeString = sizeString.Replace("ГБ", "GB"); sizeString = sizeString.Replace("МБ", "MB"); sizeString = sizeString.Replace("КБ", "KB"); // untested release.Size = ReleaseInfo.GetBytes(sizeString); logger.Info("> Add: " + release.Title); releases.Add(release); } catch (Exception ex) { logger.Error(string.Format("{0}: Error while parsing row '{1}':\n\n{2}", ID, row.OuterHtml, ex)); } } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }
// `detailsUrl` is a series details url provided in search JSON response. private async Task <List <ReleaseInfo> > FetchSeriesReleases(string url, TorznabQuery query, string filter) { logger.Info("FetchSeriesReleases: " + url + " S: " + query.Season.ToString() + " E: " + query.Episode + " Filter: " + filter); var releases = new List <ReleaseInfo>(); var results = await RequestStringWithCookies(url); try { var parser = new HtmlParser(); var document = parser.Parse(results.Content); var seasons = document.QuerySelectorAll("div.serie-block"); if (string.IsNullOrEmpty(query.Episode) || string.IsNullOrEmpty(filter)) { var rows = seasons.SelectMany(s => s.QuerySelectorAll("table.movie-parts-list > tbody > tr")); foreach (var row in rows) { var couldBreak = false; // Set to `true` if searched episode was found. if (!string.IsNullOrEmpty(filter)) { var titles = row.QuerySelector("td.gamma > div"); if (titles.TextContent.IndexOf(filter, StringComparison.OrdinalIgnoreCase) == -1) { continue; } } var playButton = row.QuerySelector("td.zeta > div.external-btn"); if (!string.IsNullOrEmpty(query.Episode)) { var match = parsePlayEpisodeRegex.Match(playButton.GetAttribute("onclick")); var episode = match.Groups["episode"]; if (episode == null || episode.Value != query.Episode) { continue; } couldBreak = true; } var dateColumn = row.QuerySelector("td.delta"); // Contains both Date and EpisodeURL var link = dateColumn.GetAttribute("onclick"); // goTo('/series/Prison_Break/season_5/episode_9/',false) link = TrimString(link, '\'', '\''); var episodeUrl = SiteLink + link.TrimStart('/'); var comments = new Uri(episodeUrl); var dateString = dateColumn.QuerySelector("span").TextContent; dateString = dateString.Substring(dateString.IndexOf(":") + 2); // 'Eng: 23.05.2017' -> '23.05.2017' var date = DateTime.Parse(dateString, new CultureInfo(Language)); // dd.mm.yyyy var urlDetails = new TrackerUrlDetails(playButton); var episodeReleases = await FetchTrackerReleases(urlDetails); foreach (var release in episodeReleases) { release.Comments = comments; release.PublishDate = date; } releases.AddRange(episodeReleases); if (couldBreak) { break; } } } else if (query.Season > 0) { // Query for the whole season release. Strange query in terms of typical requests but doable. var buttons = seasons.SelectMany(s => s.QuerySelectorAll("div.movie-details-block > div.external-btn")); foreach (var playButton in buttons) { var match = parsePlayEpisodeRegex.Match(playButton.GetAttribute("onclick")); var season = match.Groups["season"]; if (season != null && season.Value == query.Season.ToString()) { var urlDetails = new TrackerUrlDetails(playButton); // TODO: Set PublishDate = season.lastEpisode.publishDate for season releases. releases = await FetchTrackerReleases(urlDetails); // Skip other seasons break; } } } else { throw new ArgumentException("Impossible combination of arguments"); } } catch (Exception ex) { OnParseError(results.Content, ex); } return(releases); }