using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using AngleSharp.Parser.Html; using Jackett.Common.Models; using Jackett.Common.Models.IndexerConfig; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; using Jackett.Common.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; using static Jackett.Common.Models.IndexerConfig.ConfigurationData; namespace Jackett.Common.Indexers { public class Newpct : BaseCachingWebIndexer { enum ReleaseType { TV, Movie, } class NewpctRelease : ReleaseInfo { public int? Season; public int? Episode; public int? EpisodeTo; } private ReleaseInfo _mostRecentRelease; private Regex _searchStringRegex = new Regex(@"(.+?)S0?(\d+)(E0?(\d+))?$", RegexOptions.IgnoreCase); private Regex _titleListRegex = new Regex(@"Serie(.+?)(Temporada(.+?)(\d+)(.+?))?Capitulos?(.+?)(\d+)((.+?)(\d+))?(.+?)-(.+?)Calidad(.*)", RegexOptions.IgnoreCase); private Regex _titleClassicRegex = new Regex(@"(\[[^\]]*\])?\[Cap\.(\d{1,2})(\d{2})(_(\d{1,2})(\d{2}))?\]", RegexOptions.IgnoreCase); private Regex _titleClassicTvQualityRegex = new Regex(@"\[([^\]]*HDTV[^\]]*)", RegexOptions.IgnoreCase); private int _maxDailyPages = 7; private int _maxEpisodesListPages = 100; private int[] _allTvCategories = TorznabCatType.TV.SubCategories.Select(c => c.ID).ToArray(); private string _dailyUrl = "/ultimas-descargas/pg/{0}"; private string[] _seriesLetterUrls = new string[] { "/series/letter/{0}", "/series-hd/letter/{0}" }; private string[] _seriesVOLetterUrls = new string[] { "/series-vo/letter/{0}" }; private string _seriesUrl = "{0}/pg/{1}"; public Newpct(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps) : base(name: "Newpct", description: "Newpct - descargar torrent peliculas, series", link: "http://www.tvsinpagar.com/", caps: new TorznabCapabilities(TorznabCatType.TV, TorznabCatType.TVSD, TorznabCatType.TVHD, TorznabCatType.Movies), configService: configService, client: wc, logger: l, p: ps, configData: new ConfigurationData()) { Encoding = Encoding.GetEncoding("windows-1252"); Language = "es-es"; Type = "public"; var voItem = new BoolItem() { Name = "Include original versions in search results", Value = false }; configData.AddDynamic("IncludeVo", voItem); } public override async Task ApplyConfiguration(JToken configJson) { configData.LoadValuesFromJson(configJson); var releases = await PerformQuery(new TorznabQuery()); await ConfigureIfOK(string.Empty, releases.Count() > 0, () => { throw new Exception("Could not find releases from this URL"); }); return IndexerConfigurationStatus.Completed; } protected override async Task> PerformQuery(TorznabQuery query) { return await PerformQuery(query, 0); } public override async Task Download(Uri link) { var results = await RequestStringWithCookies(link.AbsoluteUri); var content = results.Content; Regex regex = new Regex("[^\"]*/descargar-torrent/\\d+_[^\"]*"); Match match = regex.Match(content); if (match.Success) link = new Uri(match.Groups[0].Value); else this.logger.Warn("Newpct - download link not found in " + link); return await base.Download(link); } private async Task> PerformQuery(TorznabQuery query, int attempts) { var releases = new List(); bool rssMode = string.IsNullOrEmpty(query.SanitizedSearchTerm); Uri siteLinkUri = new Uri(configData.SiteLink.Value); if (rssMode) { int pg = 1; while (pg <= _maxDailyPages) { Uri url = new Uri(siteLinkUri, string.Format(_dailyUrl, pg)); var results = await RequestStringWithCookies(url.AbsoluteUri); var items = ParseDailyContent(results.Content); if (items == null || !items.Any()) break; releases.AddRange(items); //Check if we need to go to next page bool recentFound = _mostRecentRelease != null && items.Any(r => r.Title == _mostRecentRelease.Title && r.Link.AbsoluteUri == _mostRecentRelease.Link.AbsoluteUri); if (pg == 1) _mostRecentRelease = (ReleaseInfo)items.First().Clone(); if (recentFound) break; pg++; } } else { //Only tv search supported. (newpct web search is useless) bool isTvSearch = query.Categories == null || query.Categories.Length == 0 || query.Categories.Any(c => _allTvCategories.Contains(c)); if (isTvSearch) { var newpctReleases = new List(); string seriesName = query.SanitizedSearchTerm; int? season = query.Season > 0 ? (int?)query.Season : null; int? episode = null; if (!string.IsNullOrWhiteSpace(query.Episode) && int.TryParse(query.Episode, out int episodeTemp)) episode = episodeTemp; //If query has no season/episode info, try to parse title if (season == null && episode == null) { Match searchMatch = _searchStringRegex.Match(query.SanitizedSearchTerm); if (searchMatch.Success) { seriesName = searchMatch.Groups[1].Value.Trim(); season = int.Parse(searchMatch.Groups[2].Value); episode = searchMatch.Groups[4].Success ? (int?)int.Parse(searchMatch.Groups[4].Value) : null; } } //Try to reuse cache bool cacheFound = false; lock (cache) { CleanCache(); var cachedResult = cache.FirstOrDefault(i => i.Query == seriesName.ToLower()); if (cachedResult != null && cachedResult.Results != null) { cacheFound = true; newpctReleases = cachedResult.Results.Where(r => (r as NewpctRelease) != null).ToList(); if (!newpctReleases.Any() && cachedResult.Results.Any()) cacheFound = false; } } if (!cacheFound) { IEnumerable lettersUrl; if (!((BoolItem)configData.GetDynamic("IncludeVo")).Value) lettersUrl = _seriesLetterUrls; else lettersUrl = _seriesLetterUrls.Concat(_seriesVOLetterUrls); string seriesLetter = !char.IsDigit(seriesName[0]) ? seriesName[0].ToString() : "0-9"; //Search series url foreach (string urlFormat in lettersUrl) { Uri seriesListUrl = new Uri(siteLinkUri, string.Format(urlFormat, seriesLetter.ToLower())); var results = await RequestStringWithCookies(seriesListUrl.AbsoluteUri); //Episodes list string seriesEpisodesUrl = ParseSeriesListContent(results.Content, seriesName); if (!string.IsNullOrEmpty(seriesEpisodesUrl)) { int pg = 1; while (pg < _maxEpisodesListPages) { Uri episodesListUrl = new Uri(string.Format(_seriesUrl, seriesEpisodesUrl, pg)); results = await RequestStringWithCookies(episodesListUrl.AbsoluteUri); var items = ParseEpisodesListContent(results.Content); if (items == null || !items.Any()) break; newpctReleases.AddRange(items); pg++; } } } //Cache ALL episodes lock (cache) { cache.Add(new CachedQueryResult(seriesName.ToLower(), newpctReleases)); } } //Filter only episodes needed releases.AddRange(newpctReleases.Where(r => { NewpctRelease nr = r as NewpctRelease; return nr.Season.HasValue != season.HasValue || //Can't determine if same season nr.Season.HasValue && season.Value == nr.Season.Value && //Same season and ... ( nr.Episode.HasValue != episode.HasValue || //Can't determine if same episode nr.Episode.HasValue && ( nr.Episode.Value == episode.Value || //Same episode nr.EpisodeTo.HasValue && episode.Value >= nr.Episode.Value && episode.Value <= nr.EpisodeTo.Value //Episode in interval ) ); })); } } return releases; } private IEnumerable ParseDailyContent(string content) { var SearchResultParser = new HtmlParser(); var doc = SearchResultParser.Parse(content); List releases = new List(); try { var rows = doc.QuerySelectorAll(".content .info"); foreach (var row in rows) { var anchor = row.QuerySelector("a"); var title = anchor.TextContent.Replace("\t", "").Trim(); var detailsUrl = anchor.GetAttribute("href"); var span = row.QuerySelector("span"); var quality = span.ChildNodes[0].TextContent.Trim(); ReleaseType releaseType = ReleaseTypeFromQuality(quality); var sizeText = span.ChildNodes[1].TextContent.Replace("Tama\u00F1o", "").Trim(); var div = row.QuerySelector("div"); var language = div.ChildNodes[1].TextContent.Trim(); NewpctRelease newpctRelease; if (releaseType == ReleaseType.TV) newpctRelease = GetReleaseFromData(releaseType, string.Format("Serie {0} - {1} Calidad [{2}]", title, language, quality), detailsUrl, quality, language, ReleaseInfo.GetBytes(sizeText), DateTime.Now); else newpctRelease = GetReleaseFromData(releaseType, string.Format("{0} [{1}][{2}]", title, quality, language), detailsUrl, quality, language, ReleaseInfo.GetBytes(sizeText), DateTime.Now); releases.Add(newpctRelease); } } catch (Exception ex) { OnParseError(content, ex); } return releases; } private string ParseSeriesListContent(string content, string title) { var SearchResultParser = new HtmlParser(); var doc = SearchResultParser.Parse(content); Dictionary results = new Dictionary(); try { var rows = doc.QuerySelectorAll(".pelilist li a"); foreach (var anchor in rows) { var h2 = anchor.QuerySelector("h2"); if (h2.TextContent.Trim().ToLower() == title.Trim().ToLower()) return anchor.GetAttribute("href"); } } catch (Exception ex) { OnParseError(content, ex); } return null; } private IEnumerable ParseEpisodesListContent(string content) { var SearchResultParser = new HtmlParser(); var doc = SearchResultParser.Parse(content); List releases = new List(); try { var rows = doc.QuerySelectorAll(".content .info"); foreach (var row in rows) { var anchor = row.QuerySelector("a"); var title = anchor.TextContent.Replace("\t", "").Trim(); var detailsUrl = anchor.GetAttribute("href"); var span = row.QuerySelector("span"); var pubDateText = row.ChildNodes[3].TextContent.Trim(); var sizeText = row.ChildNodes[5].TextContent.Trim(); long size = ReleaseInfo.GetBytes(sizeText); DateTime publishDate = DateTime.ParseExact(pubDateText, "dd-MM-yyyy", null); NewpctRelease newpctRelease = GetReleaseFromData(ReleaseType.TV, title, detailsUrl, null, null, size, publishDate); releases.Add(newpctRelease); } } catch (Exception ex) { OnParseError(content, ex); } return releases; } ReleaseType ReleaseTypeFromQuality(string quality) { if (quality.Trim().ToLower().StartsWith("hdtv")) return ReleaseType.TV; else return ReleaseType.Movie; } NewpctRelease GetReleaseFromData(ReleaseType releaseType, string title, string detailsUrl, string quality, string language, long size, DateTime publishDate) { NewpctRelease result = new NewpctRelease(); //Sanitize title = title.Replace("\t", "").Replace("\x2013", "-"); Match match = _titleListRegex.Match(title); if (match.Success) { string name = match.Groups[1].Value.Trim(' ', '-'); result.Season = int.Parse(match.Groups[4].Success ? match.Groups[4].Value.Trim() : "1"); result.Episode = int.Parse(match.Groups[7].Value.Trim().PadLeft(2, '0')); result.EpisodeTo = match.Groups[10].Success ? (int?)int.Parse(match.Groups[10].Value.Trim()) : null; string audioQuality = match.Groups[12].Value.Trim(' ', '[', ']'); quality = match.Groups[13].Value.Trim(' ', '[', ']'); string seasonText = result.Season.ToString(); string episodeText = seasonText + result.Episode.ToString().PadLeft(2, '0'); string episodeToText = result.EpisodeTo.HasValue ? "_" + seasonText + result.EpisodeTo.ToString().PadLeft(2, '0') : ""; result.Title = string.Format("{0} - Temporada {1} [{2}][Cap.{3}{4}][{5}]", name, seasonText, quality, episodeText, episodeToText, audioQuality); } else { Match matchClassic = _titleClassicRegex.Match(title); if (matchClassic.Success) { result.Season = matchClassic.Groups[2].Success ? (int?)int.Parse(matchClassic.Groups[2].Value) : null; result.Episode = matchClassic.Groups[3].Success ? (int?)int.Parse(matchClassic.Groups[3].Value) : null; result.EpisodeTo = matchClassic.Groups[6].Success ? (int?)int.Parse(matchClassic.Groups[6].Value) : null; if (matchClassic.Groups[1].Success) quality = matchClassic.Groups[1].Value; } result.Title = title; } if (releaseType == ReleaseType.TV) { if (!string.IsNullOrWhiteSpace(quality) && (quality.Contains("720") || quality.Contains("1080"))) result.Category = new List { TorznabCatType.TVHD.ID }; else result.Category = new List { TorznabCatType.TV.ID }; } else { result.Title = title; result.Category = new List { TorznabCatType.Movies.ID }; } result.Size = size; result.Link = new Uri(detailsUrl); result.PublishDate = publishDate; result.Seeders = 1; result.Peers = 1; return result; } } }