diff --git a/src/NzbDrone.Core/Indexers/Definitions/PirateTheNet.cs b/src/NzbDrone.Core/Indexers/Definitions/PirateTheNet.cs new file mode 100644 index 000000000..06295fd32 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Definitions/PirateTheNet.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using AngleSharp.Html.Parser; +using Newtonsoft.Json; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Indexers.Settings; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers.Definitions; + +public class PirateTheNet : TorrentIndexerBase +{ + public override string Name => "PirateTheNet"; + public override string[] IndexerUrls => new[] { "http://piratethenet.org/" }; + public override string Description => "PirateTheNet (PTN) is a ratioless movie tracker."; + public override DownloadProtocol Protocol => DownloadProtocol.Torrent; + public override IndexerPrivacy Privacy => IndexerPrivacy.Private; + public override IndexerCapabilities Capabilities => SetCapabilities(); + private string LoginUrl => Settings.BaseUrl + "login.php"; + private string CaptchaUrl => Settings.BaseUrl + "simpleCaptcha.php?numImages=1"; + + public PirateTheNet(IIndexerHttpClient httpClient, + IEventAggregator eventAggregator, + IIndexerStatusService indexerStatusService, + IConfigService configService, + Logger logger) + : base(httpClient, eventAggregator, indexerStatusService, configService, logger) + { + } + + public override IIndexerRequestGenerator GetRequestGenerator() + { + return new PirateTheNetRequestGenerator(Settings, Capabilities); + } + + public override IParseIndexerResponse GetParser() + { + return new PirateTheNetParser(Settings, Capabilities.Categories); + } + + protected override async Task DoLogin() + { + var captchaPage = await ExecuteAuth(new HttpRequest(CaptchaUrl)); + + var captchaResponse = JsonConvert.DeserializeAnonymousType(captchaPage.Content, new + { + images = new[] { new { hash = string.Empty } } + }); + + var requestBuilder = new HttpRequestBuilder(LoginUrl) + { + LogResponseContent = true, + AllowAutoRedirect = true, + Method = HttpMethod.Post + }; + requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15); + + var authLoginRequest = requestBuilder + .SetCookies(captchaPage.GetCookies()) + .AddFormParameter("username", Settings.Username) + .AddFormParameter("password", Settings.Password) + .AddFormParameter("captchaSelection", captchaResponse.images[0].hash) + .SetHeader("Content-Type", "application/x-www-form-urlencoded") + .SetHeader("Referer", LoginUrl) + .Build(); + + var response = await ExecuteAuth(authLoginRequest); + + if (CheckIfLoginNeeded(response)) + { + throw new IndexerAuthException("Login Failed."); + } + + var cookies = response.GetCookies(); + UpdateCookies(cookies, DateTime.Now + TimeSpan.FromDays(30)); + + _logger.Debug("Authentication succeeded."); + } + + protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) + { + return !httpResponse.Content.Contains("logout.php"); + } + + private IndexerCapabilities SetCapabilities() + { + var caps = new IndexerCapabilities + { + MovieSearchParams = new List + { + MovieSearchParam.Q, MovieSearchParam.ImdbId + } + }; + + caps.Categories.AddCategoryMapping("1080P", NewznabStandardCategory.MoviesHD, "1080P"); + caps.Categories.AddCategoryMapping("2160P", NewznabStandardCategory.MoviesHD, "2160P"); + caps.Categories.AddCategoryMapping("720P", NewznabStandardCategory.MoviesHD, "720P"); + caps.Categories.AddCategoryMapping("BDRip", NewznabStandardCategory.MoviesSD, "BDRip"); + caps.Categories.AddCategoryMapping("BluRay", NewznabStandardCategory.MoviesBluRay, "BluRay"); + caps.Categories.AddCategoryMapping("BRRip", NewznabStandardCategory.MoviesSD, "BRRip"); + caps.Categories.AddCategoryMapping("DVDR", NewznabStandardCategory.MoviesDVD, "DVDR"); + caps.Categories.AddCategoryMapping("DVDRip", NewznabStandardCategory.MoviesSD, "DVDRip"); + caps.Categories.AddCategoryMapping("FLAC", NewznabStandardCategory.AudioLossless, "FLAC OST"); + caps.Categories.AddCategoryMapping("MP3", NewznabStandardCategory.AudioMP3, "MP3 OST"); + caps.Categories.AddCategoryMapping("MP4", NewznabStandardCategory.MoviesOther, "MP4"); + caps.Categories.AddCategoryMapping("Packs", NewznabStandardCategory.MoviesOther, "Packs"); + caps.Categories.AddCategoryMapping("R5", NewznabStandardCategory.MoviesDVD, "R5 / SCR"); + caps.Categories.AddCategoryMapping("Remux", NewznabStandardCategory.MoviesOther, "Remux"); + caps.Categories.AddCategoryMapping("TVRip", NewznabStandardCategory.MoviesOther, "TVRip"); + caps.Categories.AddCategoryMapping("WebRip", NewznabStandardCategory.MoviesWEBDL, "WebRip"); + + return caps; + } +} + +public class PirateTheNetRequestGenerator : IIndexerRequestGenerator +{ + private readonly UserPassTorrentBaseSettings _settings; + private readonly IndexerCapabilities _capabilities; + + public PirateTheNetRequestGenerator(UserPassTorrentBaseSettings settings, IndexerCapabilities capabilities) + { + _settings = settings; + _capabilities = capabilities; + } + + public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.FullImdbId)); + + return pageableRequests; + } + + public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria) + { + return new IndexerPageableRequestChain(); + } + + public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria) + { + var pageableRequests = new IndexerPageableRequestChain(); + + pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories)); + + return pageableRequests; + } + + private IEnumerable GetPagedRequests(string term, int[] categories, string imdbId = null) + { + var parameters = new NameValueCollection + { + { "action", "torrentstable" }, + { "viewtype", "0" }, + { "visiblecategories", "Action,Adventure,Animation,Biography,Comedy,Crime,Documentary,Drama,Family,Fantasy,History,Horror,Kids,Music,Mystery,Packs,Romance,Sci-Fi,Short,Sports,Thriller,War,Western" }, + { "page", "1" }, + { "visibility", "showall" }, + { "compression", "showall" }, + { "sort", "added" }, + { "order", "DESC" }, + { "titleonly", "true" }, + { "packs", "showall" }, + { "bookmarks", "showall" }, + { "subscriptions", "showall" }, + { "skw", "showall" } + }; + + if (imdbId.IsNotNullOrWhiteSpace()) + { + parameters.Set("advancedsearchparameters", $"[imdb={imdbId}]"); + } + else if (term.IsNotNullOrWhiteSpace()) + { + parameters.Set("searchstring", term); + } + + var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories); + if (queryCats.Any()) + { + parameters.Set("hiddenqualities", string.Join(",", queryCats)); + } + + var searchUrl = _settings.BaseUrl + "torrentsutils.php"; + + if (parameters.Count > 0) + { + searchUrl += $"?{parameters.GetQueryString()}"; + } + + var request = new IndexerRequest(searchUrl, HttpAccept.Html); + + yield return request; + } + + public Func> GetCookies { get; set; } + public Action, DateTime?> CookiesUpdater { get; set; } +} + +public class PirateTheNetParser : IParseIndexerResponse +{ + private readonly UserPassTorrentBaseSettings _settings; + private readonly IndexerCapabilitiesCategories _categories; + + public PirateTheNetParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories) + { + _settings = settings; + _categories = categories; + } + + public IList ParseResponse(IndexerResponse indexerResponse) + { + var releaseInfos = new List(); + + var parser = new HtmlParser(); + var dom = parser.ParseDocument(indexerResponse.Content); + + var rows = dom.QuerySelectorAll("table.main > tbody > tr"); + foreach (var row in rows.Skip(1)) + { + var qDetails = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)"); + var title = qDetails?.GetAttribute("alt")?.Trim(); + + var infoUrl = _settings.BaseUrl + qDetails?.GetAttribute("href")?.TrimStart('/'); + var downloadUrl = _settings.BaseUrl + row.QuerySelector("td > a:has(img[alt=\"Download Torrent\"])")?.GetAttribute("href")?.TrimStart('/'); + + var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)")?.TextContent); + var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)")?.TextContent); + + var cat = row.QuerySelector("td:nth-of-type(1) > a > img")?.GetAttribute("src")?.Split('/').Last().Split('.').First() ?? "packs"; + + var release = new TorrentInfo + { + Guid = infoUrl, + InfoUrl = infoUrl, + DownloadUrl = downloadUrl, + Title = title, + Categories = _categories.MapTrackerCatToNewznab(cat), + Seeders = seeders, + Peers = seeders + leechers, + Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(7)")?.TextContent.Trim()), + Files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(4)")?.TextContent), + Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)")?.TextContent), + DownloadVolumeFactor = 0, // ratioless + UploadVolumeFactor = 1, + MinimumRatio = 1, + MinimumSeedTime = 259200, // 72 hours + }; + + var added = row.QuerySelector("td:nth-of-type(6) > nobr")?.TextContent.Trim(); + if (added.StartsWith("Today ")) + { + release.PublishDate = DateTime.Now.Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay; + } + else if (added.StartsWith("Yesterday ")) + { + release.PublishDate = DateTime.Now.Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay - TimeSpan.FromDays(1); + } + else + { + release.PublishDate = DateTime.ParseExact(added, "MMM d yyyy hh:mm tt", CultureInfo.InvariantCulture); + } + + releaseInfos.Add(release); + } + + return releaseInfos.ToArray(); + } + + public Action, DateTime?> CookiesUpdater { get; set; } +}