diff --git a/README.md b/README.md index d0b3cad4d..3ee20eff3 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/ * Orpheus * Ourbits (HDPter) * P2PBG + * Partis * Passione Torrent * PassThePopcorn (PTP) * Peers.FM diff --git a/src/Jackett.Common/Indexers/Partis.cs b/src/Jackett.Common/Indexers/Partis.cs new file mode 100644 index 000000000..28200461a --- /dev/null +++ b/src/Jackett.Common/Indexers/Partis.cs @@ -0,0 +1,226 @@ +using AngleSharp.Html.Parser; +using CsQuery; +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 System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett.Common.Indexers +{ + public class Partis : BaseWebIndexer + { + private string LoginUrl { get { return SiteLink + "user/login/"; } } + private string SearchUrl { get { return SiteLink + "torrent/show/"; } } + + private new ConfigurationDataBasicLogin configData + { + get { return (ConfigurationDataBasicLogin)base.configData; } + set { base.configData = value; } + } + + public Partis(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps) + : base(name: "Partis", + description: "Partis is a SLOVENIAN Private Torrent Tracker", + link: "https://www.partis.si/", + caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + configService: configService, + client: wc, + logger: l, + p: ps, + configData: new ConfigurationDataBasicLogin()) + { + Encoding = Encoding.UTF8; + Language = "sl-sl"; + Type = "private"; + + // Blu Ray + AddCategoryMapping(40, TorznabCatType.MoviesBluRay, "Blu-Ray 1080p/i"); + AddCategoryMapping(42, TorznabCatType.MoviesBluRay, "Blu-Ray 720p/i"); + AddCategoryMapping(43, TorznabCatType.MoviesBluRay, "Blu-Ray B-Disc"); + AddCategoryMapping(41, TorznabCatType.MoviesBluRay, "Blu-Ray 3D"); + AddCategoryMapping(44, TorznabCatType.MoviesBluRay, "Blu-Ray Remux"); + AddCategoryMapping(45, TorznabCatType.MoviesBluRay, "Blu-Ray Remux/Disc"); + + // UHD + AddCategoryMapping(32, TorznabCatType.MoviesUHD, "UHD 4K Disc"); + AddCategoryMapping(55, TorznabCatType.MoviesUHD, "UHD 4K Remux"); + + // HD + AddCategoryMapping(20, TorznabCatType.MoviesHD, "HD"); + AddCategoryMapping(4, TorznabCatType.MoviesSD, "DVD-R"); + AddCategoryMapping(7, TorznabCatType.MoviesSD, "XviD"); + AddCategoryMapping(12, TorznabCatType.MoviesSD, "Anime"); + AddCategoryMapping(30, TorznabCatType.MoviesSD, "Risanke"); + AddCategoryMapping(15, TorznabCatType.MoviesSD, "Sport"); + + // TV Show + AddCategoryMapping(53, TorznabCatType.TVWEBDL, "TV WEB-DL"); + AddCategoryMapping(60, TorznabCatType.TVSD, "TV-XviD"); + AddCategoryMapping(38, TorznabCatType.TVSD, "SD-TV"); + AddCategoryMapping(51, TorznabCatType.TVHD, "TV 1080p/i"); + AddCategoryMapping(52, TorznabCatType.TVHD, "TV 720p/i"); + + AddCategoryMapping(54, TorznabCatType.MoviesSD, "WEBRip"); + AddCategoryMapping(59, TorznabCatType.MoviesWEBDL, "WEB-DL"); + AddCategoryMapping(24, TorznabCatType.TVDocumentary, "Dokumentarci"); + + // Games + AddCategoryMapping(10, TorznabCatType.PCGames, "PC igre/ISO"); + AddCategoryMapping(11, TorznabCatType.PCGames, "PC igre/Rips/Repack"); + AddCategoryMapping(64, TorznabCatType.PCGames, "PC igre/Update & Patch"); + + // Music + AddCategoryMapping(46, TorznabCatType.AudioLossless, "Glasba/Flac"); + AddCategoryMapping(8, TorznabCatType.AudioOther, "Glasba/Ostalo"); + AddCategoryMapping(47, TorznabCatType.AudioMP3, "Glasba/Mp3"); + AddCategoryMapping(8, TorznabCatType.AudioVideo, "Music DVD"); + AddCategoryMapping(8, TorznabCatType.AudioVideo, "Videospoti"); + AddCategoryMapping(21, TorznabCatType.AudioAudiobook, "AudioBook"); + AddCategoryMapping(3, TorznabCatType.BooksEbook, "eKnjige"); + } + + public override async Task ApplyConfiguration(JToken configJson) + { + LoadValuesFromJson(configJson); + + var pairs = new Dictionary + { + { "user[username]", configData.Username.Value }, + { "user[password]", configData.Password.Value } + }; + + var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, String.Empty, false, null, null, true); + await ConfigureIfOK(result.Cookies, result.Content != null && result.Cookies.Contains(configData.Username.Value), () => + { + CQ dom = result.Content; + var errorMessage = dom["div.obvet > span.najvecji"].Html(); // Prijava ni uspela! obvestilo + throw new ExceptionWithConfigData(errorMessage, configData); + }); + return IndexerConfigurationStatus.RequiresTesting; + } + + protected override async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); //List of releases initialization + var searchString = query.GetQueryString(); //get search string from query + + WebClientStringResult results = null; + var queryCollection = new NameValueCollection(); + List catList = MapTorznabCapsToTrackers(query); // map categories from query to indexer specific + var categ = String.Join(",", catList); + + //create GET request - search URI + queryCollection.Add("offset", "0"); + queryCollection.Add("keyword", searchString); + queryCollection.Add("category", categ.TrimStart(',')); + queryCollection.Add("option", ""); + queryCollection.Add("ns", "true"); + + //concatenate base search url with query + var searchUrl = SearchUrl + "?" + queryCollection.GetQueryString(); + + // log search URL + logger.Info(string.Format("Searh URL Partis: {0}", searchUrl)); + + // add necessary headers + var heder = new Dictionary + { + { "X-requested-with", "XMLHttpRequest" } + }; + + //get results and follow redirect + results = await RequestStringWithCookies(searchUrl, null, SearchUrl, heder); + await FollowIfRedirect(results, null, null, null, true); + + // are we logged in? check based on cookies + if (!results.Cookies.Contains(configData.Username.Value)) + { + await ApplyConfiguration(null); + } + // another request with specific query - NEEDED for succesful response + results = await RequestStringWithCookies(SiteLink + "brskaj/?rs=false&offset=0", null, SearchUrl, heder); + await FollowIfRedirect(results, null, null, null, true); + + // parse results + try + { + string RowsSelector = "div.list > div[name=\"torrrow\"]"; + + var ResultParser = new HtmlParser(); + var SearchResultDocument = ResultParser.ParseDocument(results.Content); + var Rows = SearchResultDocument.QuerySelectorAll(RowsSelector); + foreach (var Row in Rows) + { + try + { + // initialize REleaseInfo + var release = new ReleaseInfo + { + MinimumRatio = 1, + MinimumSeedTime = 0 + }; + + // Get Category + var catega = Row.QuerySelector("div.likona div").GetAttribute("alt"); + release.Category = MapTrackerCatDescToNewznab(catega); + + var qDetailsLink = Row.QuerySelector("div.listeklink a"); + + // Title and torrent link + release.Title = qDetailsLink.TextContent; + release.Comments = new Uri(SiteLink + qDetailsLink.GetAttribute("href").TrimStart('/')); + release.Guid = release.Comments; + + // Date of torrent creation + var liopis = Row.QuerySelector("div.listeklink div span.middle"); + int ind = liopis.TextContent.IndexOf("Naloženo:"); + String reldate = liopis.TextContent.Substring(ind + 10, 22); + release.PublishDate = DateTime.ParseExact(reldate, "dd.MM.yyyy ob HH:mm:ss", CultureInfo.InvariantCulture); + + // Is freeleech? + var checkIfFree = (Row.QuerySelector("div.listeklink div.liopisl img[title=\"freeleech\"]") != null) ? true : false; + + // Download link + var qDownloadLink = Row.QuerySelector("div.data3t a").GetAttribute("href"); + release.Link = new Uri(SiteLink + qDownloadLink.TrimStart('/')); + + // Various data - size, seeders, leechers, download count + var sel = Row.QuerySelectorAll("div.datat"); + var size = sel[0].TextContent; + release.Size = ReleaseInfo.GetBytes(size); + + release.Seeders = ParseUtil.CoerceInt(sel[1].TextContent); + release.Peers = ParseUtil.CoerceInt(sel[2].TextContent) + release.Seeders; + release.Grabs = ParseUtil.CoerceLong(sel[3].TextContent); + + // Set download/upload factor + release.DownloadVolumeFactor = checkIfFree ? 0 : 1; + release.UploadVolumeFactor = 1; + + // Add current release to List + 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; + } + } +}