diff --git a/src/Jackett.Common/Indexers/Abstract/GazelleTracker.cs b/src/Jackett.Common/Indexers/Abstract/GazelleTracker.cs
index f0b245827..eac06866d 100644
--- a/src/Jackett.Common/Indexers/Abstract/GazelleTracker.cs
+++ b/src/Jackett.Common/Indexers/Abstract/GazelleTracker.cs
@@ -9,7 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Jackett.Common.Models;
-using Jackett.Common.Models.IndexerConfig;
+using Jackett.Common.Models.IndexerConfig.Bespoke;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using Jackett.Common.Utils.Clients;
@@ -22,26 +22,24 @@ namespace Jackett.Common.Indexers.Abstract
[ExcludeFromCodeCoverage]
public abstract class GazelleTracker : BaseWebIndexer
{
- protected string LoginUrl => SiteLink + "login.php";
- protected string APIUrl => SiteLink + "ajax.php";
- protected string DownloadUrl => SiteLink + "torrents.php?action=download&usetoken=" + (useTokens ? "1" : "0") + "&id=";
- protected string DetailsUrl => SiteLink + "torrents.php?torrentid=";
- protected bool supportsFreeleechTokens;
- protected bool imdbInTags;
- protected bool supportsCategories = true; // set to false if the tracker doesn't include the categories in the API search results
- protected bool useTokens = false;
+ protected virtual string LoginUrl => SiteLink + "login.php";
+ protected virtual string APIUrl => SiteLink + "ajax.php";
+ protected virtual string DownloadUrl => SiteLink + "torrents.php?action=download&usetoken=" + (useTokens ? "1" : "0") + "&id=";
+ protected virtual string DetailsUrl => SiteLink + "torrents.php?torrentid=";
+
+ protected bool useTokens;
protected string cookie = "";
- private new ConfigurationDataBasicLogin configData
- {
- get => (ConfigurationDataBasicLogin)base.configData;
- set => base.configData = value;
- }
+ private readonly bool imdbInTags;
+ private readonly bool useApiKey;
+
+ private new ConfigurationDataGazelleTracker configData => (ConfigurationDataGazelleTracker)base.configData;
protected GazelleTracker(string link, string id, string name, string description,
IIndexerConfigurationService configService, WebClient client, Logger logger,
IProtectionService p, TorznabCapabilities caps, bool supportsFreeleechTokens,
- bool imdbInTags = false, bool has2Fa = false)
+ bool imdbInTags = false, bool has2Fa = false, bool useApiKey = false,
+ string instructionMessageOptional = null)
: base(id: id,
name: name,
description: description,
@@ -51,55 +49,57 @@ namespace Jackett.Common.Indexers.Abstract
client: client,
logger: logger,
p: p,
- configData: new ConfigurationDataBasicLogin())
+ configData: new ConfigurationDataGazelleTracker(
+ has2Fa, supportsFreeleechTokens, useApiKey, instructionMessageOptional))
{
Encoding = Encoding.UTF8;
- this.supportsFreeleechTokens = supportsFreeleechTokens;
+
this.imdbInTags = imdbInTags;
-
- if (has2Fa)
- {
- var cookieHint = new ConfigurationData.DisplayItem(
- "
- (use this only if 2FA is enabled for your account)
- Login to this tracker with your browser
- Open the DevTools panel by pressing F12
- Select the Network tab
- Click on the Doc button
- Refresh the page by pressing F5
- Select the Headers tab
- Find 'cookie:' in the Request Headers section
- Copy & paste the whole cookie string to here.
")
- {
- Name = "CookieHint"
- };
- configData.AddDynamic("cookieHint", cookieHint);
- var cookieItem = new ConfigurationData.StringItem { Value = "" };
- cookieItem.Name = "Cookie";
- configData.AddDynamic("cookie", cookieItem);
- }
-
- if (supportsFreeleechTokens)
- {
- var useTokenItem = new ConfigurationData.BoolItem { Value = false };
- useTokenItem.Name = "Use Freeleech Tokens when available";
- configData.AddDynamic("usetoken", useTokenItem);
- }
+ this.useApiKey = useApiKey;
}
public override void LoadValuesFromJson(JToken jsonConfig, bool useProtectionService = false)
{
base.LoadValuesFromJson(jsonConfig, useProtectionService);
- var cookieItem = (ConfigurationData.StringItem)configData.GetDynamic("cookie");
+ var cookieItem = configData.CookieItem;
if (cookieItem != null)
- {
cookie = cookieItem.Value;
- }
- var useTokenItem = (ConfigurationData.BoolItem)configData.GetDynamic("usetoken");
+ var useTokenItem = configData.UseTokenItem;
if (useTokenItem != null)
- {
useTokens = useTokenItem.Value;
- }
-
}
public override async Task ApplyConfiguration(JToken configJson)
{
LoadValuesFromJson(configJson);
+ if (useApiKey)
+ {
+ var apiKey = configData.ApiKey;
+ if (apiKey?.Value == null)
+ throw new Exception("Invalid API Key configured");
+ if (apiKey.Value.Length != 41)
+ throw new Exception($"Invalid API Key configured: expected length: 41, got {apiKey.Value.Length}");
+
+ try
+ {
+ var results = await PerformQuery(new TorznabQuery());
+ if (!results.Any())
+ throw new Exception("Found 0 results in the tracker");
+
+ IsConfigured = true;
+ SaveConfig();
+ return IndexerConfigurationStatus.Completed;
+ }
+ catch (Exception e)
+ {
+ IsConfigured = false;
+ throw new Exception($"Your API Key did not work: {e.Message}");
+ }
+ }
+
var pairs = new Dictionary {
{ "username", configData.Username.Value },
{ "password", configData.Password.Value },
@@ -114,9 +114,7 @@ namespace Jackett.Common.Indexers.Abstract
{
var results = await PerformQuery(new TorznabQuery());
if (!results.Any())
- {
throw new Exception("Found 0 results in the tracker");
- }
IsConfigured = true;
SaveConfig();
@@ -125,7 +123,7 @@ namespace Jackett.Common.Indexers.Abstract
catch (Exception e)
{
IsConfigured = false;
- throw new Exception("Your cookie did not work: " + e.Message);
+ throw new Exception($"Your cookie did not work: {e.Message}");
}
}
@@ -162,7 +160,6 @@ namespace Jackett.Common.Indexers.Abstract
{ "order_way", "desc" }
};
-
if (!string.IsNullOrWhiteSpace(query.ImdbID))
{
if (imdbInTags)
@@ -171,9 +168,7 @@ namespace Jackett.Common.Indexers.Abstract
queryCollection.Add("cataloguenumber", query.ImdbID);
}
else if (!string.IsNullOrWhiteSpace(searchString))
- {
queryCollection.Add("searchstr", searchString);
- }
if (query.Artist != null)
queryCollection.Add("artistname", query.Artist);
@@ -187,18 +182,17 @@ namespace Jackett.Common.Indexers.Abstract
if (query.Album != null)
queryCollection.Add("groupname", query.Album);
- if (supportsCategories)
- {
- foreach (var cat in MapTorznabCapsToTrackers(query))
- {
- queryCollection.Add("filter_cat[" + cat + "]", "1");
- }
- }
+ foreach (var cat in MapTorznabCapsToTrackers(query))
+ queryCollection.Add("filter_cat[" + cat + "]", "1");
searchUrl += "?" + queryCollection.GetQueryString();
- var response = await RequestWithCookiesAndRetryAsync(searchUrl);
- if (response.IsRedirect)
+ var apiKey = configData.ApiKey;
+ var headers = apiKey != null ? new Dictionary { ["Authorization"] = apiKey.Value } : null;
+
+ var response = await RequestWithCookiesAndRetryAsync(searchUrl, headers: headers);
+ // we get a redirect in html pages and an error message in json response (api)
+ if (response.IsRedirect || (response.ContentString != null && response.ContentString.Contains("\"bad credentials\"")))
{
// re-login
await ApplyConfiguration(null);
@@ -241,14 +235,11 @@ namespace Jackett.Common.Indexers.Abstract
if (imdbInTags)
- {
release.Imdb = tags
.Select(tag => ParseUtil.GetImdbID((string)tag))
.Where(tag => tag != null).FirstIfSingleOrDefault();
- }
if (r["torrents"] is JArray)
- {
foreach (JObject torrent in r["torrents"])
{
var release2 = (ReleaseInfo)release.Clone();
@@ -256,7 +247,6 @@ namespace Jackett.Common.Indexers.Abstract
if (ReleaseInfoPostParse(release2, torrent, r))
releases.Add(release2);
}
- }
else
{
FillReleaseInfoFromJson(release, r);
@@ -282,9 +272,7 @@ namespace Jackett.Common.Indexers.Abstract
var time = (string)torrent["time"];
if (!string.IsNullOrEmpty(time))
- {
release.PublishDate = DateTime.ParseExact(time + " +0000", "yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture);
- }
var flags = new List();
@@ -365,14 +353,10 @@ namespace Jackett.Common.Indexers.Abstract
release.DownloadVolumeFactor = 1;
release.UploadVolumeFactor = 1;
if ((bool)torrent["isFreeleech"])
- {
release.DownloadVolumeFactor = 0;
- }
var isPersonalFreeleech = (bool?)torrent["isPersonalFreeleech"];
if (isPersonalFreeleech != null && isPersonalFreeleech == true)
- {
release.DownloadVolumeFactor = 0;
- }
if ((bool)torrent["isNeutralLeech"])
{
release.DownloadVolumeFactor = 0;
@@ -382,7 +366,10 @@ namespace Jackett.Common.Indexers.Abstract
public override async Task Download(Uri link)
{
- var content = await base.Download(link);
+ var apiKey = configData.ApiKey;
+ var headers = apiKey != null ? new Dictionary { ["Authorization"] = apiKey.Value } : null;
+
+ var content = await base.Download(link, RequestType.GET, headers: headers);
// Check if we're out of FL tokens/torrent is to large
// most gazelle trackers will simply return the torrent anyway but e.g. redacted will return an error
@@ -398,7 +385,7 @@ namespace Jackett.Common.Indexers.Abstract
{
// download again with usetoken=0
var requestLinkNew = requestLink.Replace("usetoken=1", "usetoken=0");
- content = await base.Download(new Uri(requestLinkNew));
+ content = await base.Download(new Uri(requestLinkNew), RequestType.GET, headers: headers);
}
}
diff --git a/src/Jackett.Common/Indexers/BaseIndexer.cs b/src/Jackett.Common/Indexers/BaseIndexer.cs
index f39932254..bbeafcdba 100644
--- a/src/Jackett.Common/Indexers/BaseIndexer.cs
+++ b/src/Jackett.Common/Indexers/BaseIndexer.cs
@@ -381,7 +381,7 @@ namespace Jackett.Common.Indexers
return await Download(uncleanLink, RequestType.GET);
}
- protected async Task Download(Uri link, RequestType method, string refererlink = null)
+ protected async Task Download(Uri link, RequestType method, string refererlink = null, Dictionaryheaders = null)
{
// return magnet link
if (link.Scheme == "magnet")
@@ -392,11 +392,8 @@ namespace Jackett.Common.Indexers
.Replace("(", "%28")
.Replace(")", "%29")
.Replace("'", "%27");
- var response = await RequestWithCookiesAndRetryAsync(requestLink, null, method, requestLink);
+ var response = await RequestWithCookiesAndRetryAsync(requestLink, null, method, refererlink, null, headers);
- // if referer link is provied it will be used
- if (refererlink != null)
- response = await RequestWithCookiesAndRetryAsync(requestLink, null, method, refererlink);
if (response.IsRedirect)
{
await FollowIfRedirect(response);
diff --git a/src/Jackett.Common/Indexers/Redacted.cs b/src/Jackett.Common/Indexers/Redacted.cs
index b5f0b2ed0..824ba4aa6 100644
--- a/src/Jackett.Common/Indexers/Redacted.cs
+++ b/src/Jackett.Common/Indexers/Redacted.cs
@@ -5,14 +5,16 @@ using System.Threading.Tasks;
using Jackett.Common.Indexers.Abstract;
using Jackett.Common.Models;
using Jackett.Common.Services.Interfaces;
-using Jackett.Common.Utils.Clients;
using NLog;
+using WebClient = Jackett.Common.Utils.Clients.WebClient;
namespace Jackett.Common.Indexers
{
[ExcludeFromCodeCoverage]
public class Redacted : GazelleTracker
{
+ protected override string DownloadUrl => SiteLink + "ajax.php?action=download&usetoken=" + (useTokens ? "1" : "0") + "&id=";
+
public Redacted(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps)
: base(id: "redacted",
name: "Redacted",
@@ -27,7 +29,10 @@ namespace Jackett.Common.Indexers
logger: l,
p: ps,
supportsFreeleechTokens: true,
- has2Fa: true)
+ has2Fa: false,
+ useApiKey: true,
+ instructionMessageOptional: "- Go to Redacted's site and open your account settings.
- Go to Access Settings tab and copy the API Key.
- Ensure that you've checked Confirm API Key.
- Finally, click Save Profile.
"
+ )
{
Language = "en-us";
Type = "private";
@@ -48,5 +53,6 @@ namespace Jackett.Common.Indexers
results = results.Where(release => query.MatchQueryStringAND(release.Title));
return results;
}
+
}
}
diff --git a/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataGazelleTracker.cs b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataGazelleTracker.cs
new file mode 100644
index 000000000..99e7b8cba
--- /dev/null
+++ b/src/Jackett.Common/Models/IndexerConfig/Bespoke/ConfigurationDataGazelleTracker.cs
@@ -0,0 +1,49 @@
+namespace Jackett.Common.Models.IndexerConfig.Bespoke
+{
+ public class ConfigurationDataGazelleTracker : ConfigurationData
+ {
+ public StringItem Username { get; private set; }
+ public StringItem Password { get; private set; }
+ public StringItem ApiKey { get; private set; }
+ public DisplayItem CookieHint { get; private set; }
+ public StringItem CookieItem { get; private set; }
+ public BoolItem UseTokenItem { get; private set; }
+ public DisplayItem Instructions { get; private set; }
+
+ public ConfigurationDataGazelleTracker(bool has2Fa = false, bool supportsFreeleechToken = false,
+ bool useApiKey = false, string instructionMessageOptional = null)
+ {
+ if (useApiKey)
+ ApiKey = new StringItem { Name = "APIKey" };
+ else
+ {
+ Username = new StringItem { Name = "Username" };
+ Password = new StringItem { Name = "Password" };
+ }
+
+ if (has2Fa)
+ {
+ CookieHint = new DisplayItem(
+ @"Use the Cookie field only if 2FA is enabled for your account, let it empty otherwise.
+- Login to this tracker with your browser
+
- Open the DevTools panel by pressing F12
+
- Select the Network tab
+
- Click on the Doc button
+
- Refresh the page by pressing F5
+
- Select the Headers tab
+
- Find 'cookie:' in the Request Headers section
+
- Copy & paste the whole cookie string to here.
")
+ {
+ Name = "CookieHint"
+ };
+ CookieItem = new StringItem { Name = "Cookie", Value = "" };
+ }
+
+ if (supportsFreeleechToken)
+ UseTokenItem = new BoolItem { Name = "Use Freeleech Tokens when Available", Value = false };
+
+ Instructions = new DisplayItem(instructionMessageOptional) { Name = "" };
+ }
+
+ }
+}
diff --git a/src/Jackett.Common/Models/IndexerConfig/ConfigurationData.cs b/src/Jackett.Common/Models/IndexerConfig/ConfigurationData.cs
index db6583c6c..d999fe30b 100644
--- a/src/Jackett.Common/Models/IndexerConfig/ConfigurationData.cs
+++ b/src/Jackett.Common/Models/IndexerConfig/ConfigurationData.cs
@@ -169,6 +169,7 @@ namespace Jackett.Common.Models.IndexerConfig
.GetProperties()
.Where(p => p.CanRead)
.Where(p => p.PropertyType.IsSubclassOf(typeof(Item)))
+ .Where(p => p.GetValue(this) != null)
.Select(p => (Item)p.GetValue(this)).ToList();
// remove/insert Site Link manualy to make sure it shows up first