diff --git a/src/Jackett.Common/Indexers/Nebulance.cs b/src/Jackett.Common/Indexers/Nebulance.cs
index e6205f7f6..812ebce02 100644
--- a/src/Jackett.Common/Indexers/Nebulance.cs
+++ b/src/Jackett.Common/Indexers/Nebulance.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -11,7 +11,6 @@ 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;
@@ -21,9 +20,9 @@ namespace Jackett.Common.Indexers
public class Nebulance : BaseWebIndexer
{
private string LoginUrl => SiteLink + "login.php";
- private string SearchUrl => SiteLink + "torrents.php?action=basic&order_by=time&order_way=desc&search_type=0&taglist=&tags_type=0";
+ private string SearchUrl => SiteLink + "torrents.php";
- private new ConfigurationDataBasicLogin configData => (ConfigurationDataBasicLogin)base.configData;
+ private new ConfigurationDataBasicLoginWith2FA configData => (ConfigurationDataBasicLoginWith2FA)base.configData;
public Nebulance(IIndexerConfigurationService configService, Utils.Clients.WebClient c, Logger l, IProtectionService ps)
: base(id: "nebulance",
@@ -35,7 +34,9 @@ namespace Jackett.Common.Indexers
client: c,
logger: l,
p: ps,
- configData: new ConfigurationDataBasicLogin("For best results, change the 'Torrents per page' setting to 100 in your profile on the NBL webpage."))
+ configData: new ConfigurationDataBasicLoginWith2FA(@"If 2FA is disabled, let the field empty.
+ We recommend to disable 2FA because re-login will require manual actions.
+
For best results, change the 'Torrents per page' setting to 100 in your profile on the NBL webpage."))
{
Encoding = Encoding.UTF8;
Language = "en-us";
@@ -54,6 +55,7 @@ namespace Jackett.Common.Indexers
var pairs = new Dictionary {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
+ { "twofa", configData.TwoFactorAuth.Value },
{ "keeplogged", "on" },
{ "login", "Login" }
};
@@ -74,39 +76,33 @@ namespace Jackett.Common.Indexers
}
protected override async Task> PerformQuery(TorznabQuery query)
- {
- var loggedInCheck = await RequestWithCookiesAsync(SearchUrl);
- if (!loggedInCheck.ContentString.Contains("logout.php")) // re-login
- await DoLogin();
-
- // #6413
- var url = $"{SearchUrl}&searchtext={WebUtility.UrlEncode(query.GetQueryString())}";
-
- var response = await RequestWithCookiesAndRetryAsync(url);
- var releases = ParseResponse(response.ContentString);
-
- return releases;
- }
-
- private List ParseResponse(string htmlResponse)
{
var releases = new List();
+ var qc = new NameValueCollection
+ {
+ {"action", "basic"},
+ {"order_by", "time"},
+ {"order_way", "desc"},
+ {"searchtext", query.GetQueryString()}
+ };
+
+ var searchUrl = SearchUrl + "?" + qc.GetQueryString();
+ var response = await RequestWithCookiesAsync(searchUrl);
+ if (!response.ContentString.Contains("logout.php")) // re-login
+ {
+ await DoLogin();
+ response = await RequestWithCookiesAsync(searchUrl);
+ }
+
try
{
- var globalFreeleech = false;
var parser = new HtmlParser();
- var document = parser.ParseDocument(htmlResponse);
-
- if (document.QuerySelector("div.nicebar > span:contains(\"Personal Freeleech\")") != null)
- globalFreeleech = true;
-
+ var document = parser.ParseDocument(response.ContentString);
var rows = document.QuerySelectorAll(".torrent_table > tbody > tr[class^='torrent row']");
foreach (var row in rows)
{
- var release = new ReleaseInfo();
-
var title = row.QuerySelector("a[data-src]").GetAttribute("data-src");
if (string.IsNullOrEmpty(title) || title == "0")
{
@@ -119,42 +115,51 @@ namespace Jackett.Common.Indexers
title = title.Remove(title.LastIndexOf(".", StringComparison.Ordinal));
}
- release.Title = title;
- release.Description = release.Title;
- release.Guid = new Uri(SiteLink + row.QuerySelector("a[data-src]").GetAttribute("href"));
- release.Comments = release.Guid;
- release.Link = new Uri(SiteLink + row.QuerySelector("a[href*='action=download']").GetAttribute("href"));
- release.Category = new List { TvCategoryParser.ParseTvShowQuality(release.Title) };
+ var bannerStr = row.QuerySelector("img")?.GetAttribute("src");
+ var bannerUri = !string.IsNullOrWhiteSpace(bannerStr) ? new Uri(bannerStr) : null;
- var timeAnchor = row.QuerySelector("span[class='time']");
- var publishdate = timeAnchor.GetAttribute("title");
- release.PublishDate = !string.IsNullOrEmpty(publishdate) && publishdate.Contains(",")
- ? DateTime.ParseExact(publishdate, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)
- : DateTime.ParseExact(timeAnchor.TextContent.Trim(), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal);
- release.Seeders = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.TextContent.Trim());
- release.Peers = ParseUtil.CoerceInt(timeAnchor.ParentElement.NextElementSibling.NextElementSibling.NextElementSibling.TextContent.Trim()) + release.Seeders;
- release.Size = ReleaseInfo.GetBytes(timeAnchor.ParentElement.PreviousElementSibling.TextContent);
- release.MinimumRatio = 1;
- release.MinimumSeedTime = 172800; // 48 hours
+ var commentsUri = new Uri(SiteLink + row.QuerySelector("a[data-src]").GetAttribute("href"));
+ var linkUri = new Uri(SiteLink + row.QuerySelector("a[href*='action=download']").GetAttribute("href"));
- release.Files = ParseUtil.CoerceLong(row.QuerySelector("td > div:contains(\"Files:\")").TextContent.Split(':')[1].Trim());
- release.Grabs = ParseUtil.CoerceLong(row.QuerySelector("td:nth-last-child(3)").TextContent);
+ var qColSize = row.QuerySelector("td:nth-child(3)");
+ var size = ReleaseInfo.GetBytes(qColSize.Children[0].TextContent);
+ var files = ParseUtil.CoerceLong(qColSize.Children[1].TextContent.Split(':')[1].Trim());
- if (globalFreeleech)
- release.DownloadVolumeFactor = 0;
- else if (row.QuerySelector("img[alt=\"Freeleech\"]") != null)
- release.DownloadVolumeFactor = 0;
- else
- release.DownloadVolumeFactor = 1;
- release.UploadVolumeFactor = 1;
+ var qPublishdate = row.QuerySelector("td:nth-child(4) span");
+ var publishDateStr = qPublishdate.GetAttribute("title");
+ var publishDate = !string.IsNullOrEmpty(publishDateStr) && publishDateStr.Contains(",")
+ ? DateTime.ParseExact(publishDateStr, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture)
+ : DateTime.ParseExact(qPublishdate.TextContent.Trim(), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture);
+
+ var grabs = ParseUtil.CoerceLong(row.QuerySelector("td:nth-child(5)").TextContent);
+ var seeds = ParseUtil.CoerceLong(row.QuerySelector("td:nth-child(6)").TextContent);
+ var leechers = ParseUtil.CoerceLong(row.QuerySelector("td:nth-child(7)").TextContent);
+
+ var release = new ReleaseInfo
+ {
+ Title = title,
+ Guid = commentsUri,
+ Comments = commentsUri,
+ Link = linkUri,
+ Category = new List { TvCategoryParser.ParseTvShowQuality(title) },
+ Size = size,
+ Files = files,
+ PublishDate = publishDate,
+ Grabs = grabs,
+ Seeders = seeds,
+ Peers = seeds + leechers,
+ BannerUrl = bannerUri,
+ DownloadVolumeFactor = 0, // ratioless tracker
+ UploadVolumeFactor = 1
+ };
releases.Add(release);
}
}
- catch (Exception ex)
+ catch (Exception e)
{
- OnParseError(htmlResponse, ex);
+ OnParseError(response.ContentString, e);
}
return releases;
diff --git a/src/Jackett.Common/Models/IndexerConfig/ConfigurationDataBasicLoginWith2FA.cs b/src/Jackett.Common/Models/IndexerConfig/ConfigurationDataBasicLoginWith2FA.cs
new file mode 100644
index 000000000..debcf9913
--- /dev/null
+++ b/src/Jackett.Common/Models/IndexerConfig/ConfigurationDataBasicLoginWith2FA.cs
@@ -0,0 +1,18 @@
+namespace Jackett.Common.Models.IndexerConfig
+{
+ public class ConfigurationDataBasicLoginWith2FA : ConfigurationData
+ {
+ public StringItem Username { get; private set; }
+ public StringItem Password { get; private set; }
+ public StringItem TwoFactorAuth { get; private set; }
+ public DisplayItem Instructions { get; private set; }
+
+ public ConfigurationDataBasicLoginWith2FA(string instructionMessageOptional = null)
+ {
+ Username = new StringItem { Name = "Username" };
+ Password = new StringItem { Name = "Password" };
+ TwoFactorAuth = new StringItem { Name = "Two-Factor Auth" };
+ Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
+ }
+ }
+}