Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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>());
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        // `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);
        }