mirror of
https://github.com/Jackett/Jackett
synced 2025-02-23 14:51:01 +00:00
Add spanish tracker Newpct (#3077)
* Rss mode working * Fully working. Beta testing * Added vo and minor changes * Alternative link support
This commit is contained in:
parent
f63f1361ce
commit
ef22d43f46
2 changed files with 419 additions and 0 deletions
|
@ -43,6 +43,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
|||
* LimeTorrents
|
||||
* MagnetDL
|
||||
* NextTorrent
|
||||
* Newpct (aka: tvsinpagar, descargas2020, torrentlocura, torrentrapid, etc)
|
||||
* Nyaa.si
|
||||
* Nyaa-Pantsu
|
||||
* Nyoo
|
||||
|
|
418
src/Jackett.Common/Indexers/Newpct.cs
Normal file
418
src/Jackett.Common/Indexers/Newpct.cs
Normal file
|
@ -0,0 +1,418 @@
|
|||
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<IndexerConfigurationStatus> 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<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
return await PerformQuery(query, 0);
|
||||
}
|
||||
|
||||
public override async Task<byte[]> 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<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, int attempts)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
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<ReleaseInfo>();
|
||||
|
||||
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<string> 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<NewpctRelease> ParseDailyContent(string content)
|
||||
{
|
||||
var SearchResultParser = new HtmlParser();
|
||||
var doc = SearchResultParser.Parse(content);
|
||||
|
||||
List<NewpctRelease> releases = new List<NewpctRelease>();
|
||||
|
||||
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<string, string> results = new Dictionary<string, string>();
|
||||
|
||||
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<NewpctRelease> ParseEpisodesListContent(string content)
|
||||
{
|
||||
var SearchResultParser = new HtmlParser();
|
||||
var doc = SearchResultParser.Parse(content);
|
||||
|
||||
List<NewpctRelease> releases = new List<NewpctRelease>();
|
||||
|
||||
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<int> { TorznabCatType.TVHD.ID };
|
||||
else
|
||||
result.Category = new List<int> { TorznabCatType.TV.ID };
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Title = title;
|
||||
result.Category = new List<int> { TorznabCatType.Movies.ID };
|
||||
}
|
||||
|
||||
result.Size = size;
|
||||
result.Link = new Uri(detailsUrl);
|
||||
result.PublishDate = publishDate;
|
||||
result.Seeders = 1;
|
||||
result.Peers = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue