diff --git a/src/Jackett/Content/logos/nxtgn.png b/src/Jackett/Content/logos/nxtgn.png new file mode 100644 index 000000000..74d7e1ce8 Binary files /dev/null and b/src/Jackett/Content/logos/nxtgn.png differ diff --git a/src/Jackett/Controllers/PotatoController.cs b/src/Jackett/Controllers/PotatoController.cs index cf5a83fc3..34cccbcbf 100644 --- a/src/Jackett/Controllers/PotatoController.cs +++ b/src/Jackett/Controllers/PotatoController.cs @@ -121,6 +121,7 @@ namespace Jackett.Controllers var potatoResponse = new TorrentPotatoResponse(); releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year); + releases = TorznabUtil.FilterResultsToImdb(releases, request.imdbid); foreach (var r in releases) { @@ -133,7 +134,7 @@ namespace Jackett.Controllers torrent_id = release.Guid.ToString(), details_url = release.Comments.ToString(), download_url = release.Link.ToString(), - // imdb_id = request.imdbid, + imdb_id = release.Imdb.HasValue ? "tt" + release.Imdb : null, freeleech = false, type = "movie", size = (long)release.Size/ (1024 * 1024), // This is in MB diff --git a/src/Jackett/Indexers/NxtGn.cs b/src/Jackett/Indexers/NxtGn.cs new file mode 100644 index 000000000..f17659eb9 --- /dev/null +++ b/src/Jackett/Indexers/NxtGn.cs @@ -0,0 +1,213 @@ +using CsQuery; +using Jackett.Indexers; +using Jackett.Models; +using Jackett.Services; +using Jackett.Utils; +using Jackett.Utils.Clients; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.UI.WebControls; +using Jackett.Models.IndexerConfig; + +namespace Jackett.Indexers +{ + public class NxtGn : BaseIndexer, IIndexer + { + private string LoginUrl { get { return SiteLink + "login.php"; } } + private string SearchUrl { get { return SiteLink + "browse.php"; } } + private string ProfileUrl { get { return SiteLink + "my.php"; } } + + new ConfigurationDataBasicLoginWithRSS configData + { + get { return (ConfigurationDataBasicLoginWithRSS)base.configData; } + set { base.configData = value; } + } + + public NxtGn(IIndexerManagerService i, Logger l, IWebClient c, IProtectionService ps) + : base(name: "NextGen", + description: "A danish closed torrent tracker", + link: "https://nxtgn.org/", + caps: TorznabUtil.CreateDefaultTorznabTVCaps(), + manager: i, + client: c, + logger: l, + p: ps, + configData: new ConfigurationDataBasicLoginWithRSS()) + { + AddCategoryMapping(47, TorznabCatType.Movies3D); + AddCategoryMapping(38, TorznabCatType.MoviesHD); + AddCategoryMapping(38, TorznabCatType.MoviesWEBDL); + AddCategoryMapping(38, TorznabCatType.MoviesBluRay); + AddCategoryMapping(5, TorznabCatType.MoviesSD); + AddCategoryMapping(23, TorznabCatType.MoviesForeign); + AddCategoryMapping(22, TorznabCatType.MoviesSD); + //AddCategoryMapping(4, TorznabCatType.TVFOREIGN); + //AddCategoryMapping(4, TorznabCatType.TVSD); + //AddCategoryMapping(4, TorznabCatType.TVDocumentary); + //AddCategoryMapping(4, TorznabCatType.TVSport); + //AddCategoryMapping(4, TorznabCatType.TV); + //AddCategoryMapping(31, TorznabCatType.TVHD); + //AddCategoryMapping(21, TorznabCatType.TVFOREIGN); + AddCategoryMapping(46, TorznabCatType.TV); + AddCategoryMapping(46, TorznabCatType.TVHD); + //AddCategoryMapping(45, TorznabCatType.TV); + //AddCategoryMapping(45, TorznabCatType.TVSD); + //AddCategoryMapping(24, TorznabCatType.TVFOREIGN); + AddCategoryMapping(26, TorznabCatType.TV); + AddCategoryMapping(26, TorznabCatType.TVHD); + AddCategoryMapping(26, TorznabCatType.TVWEBDL); + AddCategoryMapping(33, TorznabCatType.MoviesHD); + AddCategoryMapping(33, TorznabCatType.Movies); + AddCategoryMapping(17, TorznabCatType.MoviesForeign); + AddCategoryMapping(17, TorznabCatType.MoviesDVD); + AddCategoryMapping(9, TorznabCatType.MoviesHD); + AddCategoryMapping(9, TorznabCatType.Movies); + AddCategoryMapping(9, TorznabCatType.MoviesBluRay); + AddCategoryMapping(43, TorznabCatType.TV); + AddCategoryMapping(43, TorznabCatType.TVHD); + AddCategoryMapping(43, TorznabCatType.TVWEBDL); + } + + public async Task ApplyConfiguration(JToken configJson) + { + var loginPage = await RequestStringWithCookies(LoginUrl); + CQ loginDom = loginPage.Content; + var loginPostUrl = loginDom["#login"].Attr("action"); + + configData.LoadValuesFromJson(configJson); + var pairs = new Dictionary { + { "username", configData.Username.Value }, + { "password", configData.Password.Value } + }; + // Get inital cookies + CookieHeader = string.Empty; + var response = await RequestLoginAndFollowRedirect(SiteLink + loginPostUrl, pairs, CookieHeader, true, null, LoginUrl); + + await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("Velkommen tilbage"), () => + { + CQ dom = response.Content; + var messageEl = dom["inputs"]; + var errorMessage = messageEl.Text().Trim(); + throw new ExceptionWithConfigData(errorMessage, configData); + }); + + var profilePage = await RequestStringWithCookies(ProfileUrl, response.Cookies); + CQ profileDom = profilePage.Content; + var passKey = profileDom["input[name=resetkey]"].Parent().Text(); + passKey = passKey.Substring(0, passKey.IndexOf(' ')); + configData.RSSKey.Value = passKey; + SaveConfig(); + return IndexerConfigurationStatus.RequiresTesting; + } + + public async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var breakWhile = false; + var page = 0; + while (page < 3) + { + string episodeSearchUrl; + if (string.IsNullOrEmpty(query.GetQueryString())) + { + episodeSearchUrl = SearchUrl + "?page=" + page; + breakWhile = true; + } + else + { + var cats = MapTorznabCapsToTrackers(query); + var catsUrlPart = string.Join("&", cats.Select(c => $"c{c}=1")); + episodeSearchUrl = string.Format("{0}?search={1}&cat=0&incldead=0&{2}&page={3}", SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()), catsUrlPart, page); + } + page++; + var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl); + try + { + CQ dom = results.Content; + + var rows = dom["#torrent-table-wrapper > div"]; + + foreach (var row in rows.Skip(1)) + { + var release = new ReleaseInfo(); + + var qRow = row.Cq(); + var qLink = qRow.Find("#torrent-udgivelse2-users > a").First(); + var qDesc = qRow.Find("#torrent-udgivelse2-users > p").FirstOrDefault(); + + var moviesCats = new[] { 47, 38, 5, 23, 22, 33, 17, 9 }; + var seriesCats = new[] { 46, 26, 43 }; + var catUrl = qRow.Find(".torrent-icon > a").Attr("href"); + var cat = catUrl.Substring(catUrl.LastIndexOf('=') + 1); + var catNo = int.Parse(cat); + if (moviesCats.Contains(catNo)) + release.Category = TorznabCatType.Movies.ID; + else if (seriesCats.Contains(catNo)) + release.Category = TorznabCatType.TV.ID; + else + continue; + + releases.Add(release); + + var torrentUrl = qLink.Attr("href"); + var torrentId = torrentUrl.Substring(torrentUrl.LastIndexOf('=') + 1); + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + release.Title = qLink.Attr("title"); + release.Description = qDesc != null ? qDesc.InnerText : release.Title; + release.Guid = new Uri(SiteLink + torrentUrl); + release.Comments = new Uri(release.Guid + "#startcomments"); + + var downloadUrl = $"{SiteLink}download.php?id={torrentId}&rss&passkey={configData.RSSKey.Value}"; + release.Link = new Uri(downloadUrl); + + var qAdded = qRow.Find("#torrent-added").First(); + var addedStr = qAdded.Text().Trim(); + release.PublishDate = DateTime.ParseExact(addedStr, "dd-MM-yyyyHH:mm:ss", CultureInfo.InvariantCulture); + + release.Seeders = ParseUtil.CoerceInt(qRow.Find("#torrent-seeders").Text().Trim()); + release.Peers = ParseUtil.CoerceInt(qRow.Find("#torrent-leechers").Text().Trim()) + release.Seeders; + + var sizeStr = qRow.Find("#torrent-size").First().Text(); + release.Size = ReleaseInfo.GetBytes(sizeStr); + + var infoLink = qRow.Find("#infolink"); + var linkContainer = infoLink.Children().First().Children().First(); + var url = linkContainer.Attr("href"); + var img = linkContainer.Children().First(); + var imgUrl = img.Attr("src"); + if (imgUrl == "/pic/imdb.png") + { + release.Imdb = long.Parse(url.Substring(url.LastIndexOf('t') + 1)); + } + else if (imgUrl == "/pic/TV.png") + { + release.TheTvDbId = long.Parse(url.Substring(url.LastIndexOf('=') + 1)); + } + } + var nextPage = dom["#torrent-table-wrapper + p[align=center]"].Children().Last(); + if (!nextPage.Is("a")) + breakWhile = true; + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + if (breakWhile) + break; + } + return releases; + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 71a193b74..f01f8b875 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -185,6 +185,7 @@ + @@ -448,6 +449,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Jackett/Models/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs index 4c3318075..8484ab651 100644 --- a/src/Jackett/Models/ReleaseInfo.cs +++ b/src/Jackett/Models/ReleaseInfo.cs @@ -21,6 +21,7 @@ namespace Jackett.Models public long? Size { get; set; } public string Description { get; set; } public long? RageID { get; set; } + public long? TheTvDbId { get; set; } public long? Imdb { get; set; } public int? Seeders { get; set; } public int? Peers { get; set; } diff --git a/src/Jackett/Models/ResultPage.cs b/src/Jackett/Models/ResultPage.cs index 7593ea0b6..4b70cb29b 100644 --- a/src/Jackett/Models/ResultPage.cs +++ b/src/Jackett/Models/ResultPage.cs @@ -81,6 +81,8 @@ namespace Jackett.Models ), getTorznabElement("magneturl", r.MagnetUri), getTorznabElement("rageid", r.RageID), + getTorznabElement("thetvdb", r.TheTvDbId), + getTorznabElement("imdb", r.Imdb), getTorznabElement("seeders", r.Seeders), getTorznabElement("peers", r.Peers), getTorznabElement("infohash", r.InfoHash), diff --git a/src/Jackett/Models/TorrentPotatoResponseItem.cs b/src/Jackett/Models/TorrentPotatoResponseItem.cs index 809ada2b6..9c860610a 100644 --- a/src/Jackett/Models/TorrentPotatoResponseItem.cs +++ b/src/Jackett/Models/TorrentPotatoResponseItem.cs @@ -12,7 +12,7 @@ namespace Jackett.Models public string torrent_id { get; set; } public string details_url { get; set; } public string download_url { get; set; } - // public string imdb_id { get; set; } + public string imdb_id { get; set; } public bool freeleech { get; set; } public string type { get; set; } public long size { get; set; } diff --git a/src/Jackett/Utils/JsonContent.cs b/src/Jackett/Utils/JsonContent.cs index 412962bda..17132f029 100644 --- a/src/Jackett/Utils/JsonContent.cs +++ b/src/Jackett/Utils/JsonContent.cs @@ -25,7 +25,7 @@ namespace Jackett.Utils protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { - var json = JsonConvert.SerializeObject(_value, Formatting.Indented); + var json = JsonConvert.SerializeObject(_value, Formatting.Indented, new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}); var writer = new StreamWriter(stream); writer.Write(json); await writer.FlushAsync(); diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs index 6fc28e8b5..338feea56 100644 --- a/src/Jackett/Utils/TorznabCapsUtil.cs +++ b/src/Jackett/Utils/TorznabCapsUtil.cs @@ -44,6 +44,16 @@ namespace Jackett.Utils return filteredResults; } + public static IEnumerable FilterResultsToImdb(IEnumerable results, string imdb) + { + if (string.IsNullOrWhiteSpace(imdb)) + return results; + // Filter out releases that do have a valid imdb ID, that is not equal to the one we're searching for. + return + results.Where( + result => !result.Imdb.HasValue || result.Imdb.Value == 0 || ("tt" + result.Imdb.Value).Equals(imdb)); + } + private static string CleanTitle(string title) { title = title.Replace(':', ' ').Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Replace('+', ' ');