mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-11 22:30:48 +02:00
Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ea4d0fe701 | ||
![]() |
02a57533f9 | ||
![]() |
99f0e9c9f6 | ||
![]() |
d1225c17b2 | ||
![]() |
2adecae9bf | ||
![]() |
c7aa0d7b0f | ||
![]() |
a69ecf3e06 | ||
![]() |
9414e7a4b8 | ||
![]() |
d9473781d3 | ||
![]() |
4952199e68 | ||
![]() |
388460993d | ||
![]() |
f21c1f0400 | ||
![]() |
4db62bbf63 | ||
![]() |
42ec634cd3 | ||
![]() |
a07de4d1e9 | ||
![]() |
0361a88856 | ||
![]() |
a88f248b75 | ||
![]() |
82f06d0b46 | ||
![]() |
10e019a1dd | ||
![]() |
173e26c054 | ||
![]() |
24f8d26b2d | ||
![]() |
8ceb8ebbe7 | ||
![]() |
71c583d359 | ||
![]() |
9d4e8f4bda | ||
![]() |
65d2a88591 | ||
![]() |
a04d296b55 | ||
![]() |
79452053ee | ||
![]() |
7ad5fac9c0 | ||
![]() |
2456a42608 | ||
![]() |
3c396ba880 | ||
![]() |
65e4d1e068 | ||
![]() |
e643134f09 | ||
![]() |
19bc3c7b36 | ||
![]() |
a50e31ec3f | ||
![]() |
ff96389864 | ||
![]() |
efd5a8cc98 | ||
![]() |
d5c59109b1 | ||
![]() |
4a9fcdbc9b | ||
![]() |
6bea0b009a | ||
![]() |
ff86a15ddd | ||
![]() |
d204ce6f39 | ||
![]() |
6bc011f678 | ||
![]() |
acdf198b05 | ||
![]() |
5f2dfcf499 | ||
![]() |
aa69d1a3b9 | ||
![]() |
29bde68337 | ||
![]() |
e9ae2d4cd8 | ||
![]() |
0ba6188e4b | ||
![]() |
283a003aef | ||
![]() |
a50ab9c49b | ||
![]() |
b4e42b4180 | ||
![]() |
f7efd83e09 | ||
![]() |
f67381d0ac | ||
![]() |
af0c15be2c | ||
![]() |
4ce6f6b048 | ||
![]() |
828091128e | ||
![]() |
f8c0b9a80f | ||
![]() |
f4129dc4a0 | ||
![]() |
f6f27e604a | ||
![]() |
bc1dd0e9e8 | ||
![]() |
6b1dba2dc3 | ||
![]() |
e4a47927b9 | ||
![]() |
226c27f903 | ||
![]() |
5acaf9dbb8 | ||
![]() |
a856aba953 | ||
![]() |
ce351dd3aa | ||
![]() |
99125386f0 | ||
![]() |
44d6ac2f04 | ||
![]() |
b5ff430e2d |
@@ -14,6 +14,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
|
||||
|
||||
#### Supported Private Trackers
|
||||
* Abnormal
|
||||
* AlphaRatio
|
||||
* AnimeBytes
|
||||
* Avistaz
|
||||
@@ -21,12 +22,15 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* BeyondHD
|
||||
* BIT-HDTV
|
||||
* BitMeTV
|
||||
* BitSoup
|
||||
* BlueTigers
|
||||
* BTN
|
||||
* DanishBits
|
||||
* Demonoid
|
||||
* EuTorrents
|
||||
* FileList
|
||||
* Fnt
|
||||
* French-ADN
|
||||
* Freshon
|
||||
* HD-Space
|
||||
* HD-Torrents
|
||||
@@ -38,6 +42,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* NextGen
|
||||
* Pretome
|
||||
* PrivateHD
|
||||
* RevolutionTT
|
||||
* SceneAccess
|
||||
* SceneTime
|
||||
* Shazbat
|
||||
@@ -48,6 +53,8 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
|
||||
* TorrentLeech
|
||||
* TorrentShack
|
||||
* TV Chaos UK
|
||||
* World-In-HD
|
||||
* XSpeeds
|
||||
|
||||
#### Installation on Windows
|
||||
|
||||
|
@@ -27,6 +27,9 @@ namespace Jackett.Console
|
||||
[Option('c', "UseClient", HelpText = "Override web client selection. [automatic(Default)/libcurl/safecurl/httpclient]")]
|
||||
public string Client { get; set; }
|
||||
|
||||
[Option('j', "ProxyConnection", HelpText = "use proxy - e.g. 127.0.0.1:8888")]
|
||||
public string ProxyConnection { get; set; }
|
||||
|
||||
[Option('s', "Start", HelpText = "Start the Jacket Windows service (Must be admin)")]
|
||||
public bool StartService { get; set; }
|
||||
|
||||
|
@@ -1,220 +1,226 @@
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using Jackett;
|
||||
using Jackett.Console;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JackettConsole
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new ConsoleOptions();
|
||||
if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
|
||||
{
|
||||
if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
|
||||
{
|
||||
var help = new HelpText();
|
||||
var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
|
||||
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
|
||||
Console.WriteLine("Switch error: " + errors);
|
||||
Console.WriteLine("See --help for further details on switches.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
|
||||
text.Copyright = " ";
|
||||
text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
|
||||
Console.WriteLine(text);
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (options.ListenPublic && options.ListenPrivate)
|
||||
{
|
||||
Console.WriteLine("You can only use listen private OR listen publicly.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
/* ====== Options ===== */
|
||||
|
||||
// SSL Fix
|
||||
Startup.DoSSLFix = options.SSLFix;
|
||||
|
||||
// Use curl
|
||||
if (options.Client != null)
|
||||
Startup.ClientOverride = options.Client.ToLowerInvariant();
|
||||
|
||||
// Logging
|
||||
if (options.Logging)
|
||||
Startup.LogRequests = true;
|
||||
|
||||
// Tracing
|
||||
if (options.Tracing)
|
||||
Startup.TracingEnabled = true;
|
||||
|
||||
// Log after the fact as using the logger will cause the options above to be used
|
||||
|
||||
if (options.Logging)
|
||||
Engine.Logger.Info("Logging enabled.");
|
||||
|
||||
if (options.Tracing)
|
||||
Engine.Logger.Info("Tracing enabled.");
|
||||
|
||||
if (options.SSLFix == true)
|
||||
Engine.Logger.Info("SSL ECC workaround enabled.");
|
||||
else if (options.SSLFix == false)
|
||||
Engine.Logger.Info("SSL ECC workaround has been disabled.");
|
||||
|
||||
// Ignore SSL errors on Curl
|
||||
Startup.IgnoreSslErrors = options.IgnoreSslErrors;
|
||||
if (options.IgnoreSslErrors == true)
|
||||
{
|
||||
Engine.Logger.Info("Curl will ignore SSL certificate errors.");
|
||||
}
|
||||
|
||||
/* ====== Actions ===== */
|
||||
|
||||
// Install service
|
||||
if (options.Install)
|
||||
{
|
||||
Engine.ServiceConfig.Install();
|
||||
return;
|
||||
}
|
||||
|
||||
// Uninstall service
|
||||
if (options.Uninstall)
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: false);
|
||||
Engine.ServiceConfig.Uninstall();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve urls
|
||||
if (options.ReserveUrls)
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start Service
|
||||
if (options.StartService)
|
||||
{
|
||||
if (!Engine.ServiceConfig.ServiceRunning())
|
||||
{
|
||||
Engine.ServiceConfig.Start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop Service
|
||||
if (options.StopService)
|
||||
{
|
||||
if (Engine.ServiceConfig.ServiceRunning())
|
||||
{
|
||||
Engine.ServiceConfig.Stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate settings
|
||||
if (options.MigrateSettings)
|
||||
{
|
||||
Engine.ConfigService.PerformMigration();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Show Version
|
||||
if (options.ShowVersion)
|
||||
{
|
||||
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
|
||||
return;
|
||||
}
|
||||
|
||||
/* ====== Overrides ===== */
|
||||
|
||||
// Override listen public
|
||||
if (options.ListenPublic || options.ListenPrivate)
|
||||
{
|
||||
if (Engine.Server.Config.AllowExternal != options.ListenPublic)
|
||||
{
|
||||
Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
|
||||
Engine.Server.Config.AllowExternal = options.ListenPublic;
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Unable to switch to public listening without admin rights.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Override port
|
||||
if (options.Port != 0)
|
||||
{
|
||||
if (Engine.Server.Config.Port != options.Port)
|
||||
{
|
||||
Engine.Logger.Info("Overriding port to " + options.Port);
|
||||
Engine.Server.Config.Port = options.Port;
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Unable to switch ports when not running as administrator");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.Initalize();
|
||||
Engine.Server.Start();
|
||||
Engine.RunTime.Spin();
|
||||
Engine.Logger.Info("Server thread exit");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e, "Top level exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using Jackett;
|
||||
using Jackett.Console;
|
||||
using Jackett.Indexers;
|
||||
using Jackett.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JackettConsole
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new ConsoleOptions();
|
||||
if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
|
||||
{
|
||||
if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
|
||||
{
|
||||
var help = new HelpText();
|
||||
var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
|
||||
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
|
||||
Console.WriteLine("Switch error: " + errors);
|
||||
Console.WriteLine("See --help for further details on switches.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
|
||||
text.Copyright = " ";
|
||||
text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
|
||||
Console.WriteLine(text);
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (options.ListenPublic && options.ListenPrivate)
|
||||
{
|
||||
Console.WriteLine("You can only use listen private OR listen publicly.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
/* ====== Options ===== */
|
||||
|
||||
// SSL Fix
|
||||
Startup.DoSSLFix = options.SSLFix;
|
||||
|
||||
// Use curl
|
||||
if (options.Client != null)
|
||||
Startup.ClientOverride = options.Client.ToLowerInvariant();
|
||||
|
||||
// Use Proxy
|
||||
if (options.ProxyConnection != null)
|
||||
{
|
||||
Startup.ProxyConnection = options.ProxyConnection.ToLowerInvariant();
|
||||
Engine.Logger.Info("Proxy enabled. " + Startup.ProxyConnection);
|
||||
}
|
||||
// Logging
|
||||
if (options.Logging)
|
||||
Startup.LogRequests = true;
|
||||
|
||||
// Tracing
|
||||
if (options.Tracing)
|
||||
Startup.TracingEnabled = true;
|
||||
|
||||
// Log after the fact as using the logger will cause the options above to be used
|
||||
|
||||
if (options.Logging)
|
||||
Engine.Logger.Info("Logging enabled.");
|
||||
|
||||
if (options.Tracing)
|
||||
Engine.Logger.Info("Tracing enabled.");
|
||||
|
||||
if (options.SSLFix == true)
|
||||
Engine.Logger.Info("SSL ECC workaround enabled.");
|
||||
else if (options.SSLFix == false)
|
||||
Engine.Logger.Info("SSL ECC workaround has been disabled.");
|
||||
|
||||
// Ignore SSL errors on Curl
|
||||
Startup.IgnoreSslErrors = options.IgnoreSslErrors;
|
||||
if (options.IgnoreSslErrors == true)
|
||||
{
|
||||
Engine.Logger.Info("Curl will ignore SSL certificate errors.");
|
||||
}
|
||||
|
||||
/* ====== Actions ===== */
|
||||
|
||||
// Install service
|
||||
if (options.Install)
|
||||
{
|
||||
Engine.ServiceConfig.Install();
|
||||
return;
|
||||
}
|
||||
|
||||
// Uninstall service
|
||||
if (options.Uninstall)
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: false);
|
||||
Engine.ServiceConfig.Uninstall();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve urls
|
||||
if (options.ReserveUrls)
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start Service
|
||||
if (options.StartService)
|
||||
{
|
||||
if (!Engine.ServiceConfig.ServiceRunning())
|
||||
{
|
||||
Engine.ServiceConfig.Start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop Service
|
||||
if (options.StopService)
|
||||
{
|
||||
if (Engine.ServiceConfig.ServiceRunning())
|
||||
{
|
||||
Engine.ServiceConfig.Stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate settings
|
||||
if (options.MigrateSettings)
|
||||
{
|
||||
Engine.ConfigService.PerformMigration();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Show Version
|
||||
if (options.ShowVersion)
|
||||
{
|
||||
Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
|
||||
return;
|
||||
}
|
||||
|
||||
/* ====== Overrides ===== */
|
||||
|
||||
// Override listen public
|
||||
if (options.ListenPublic || options.ListenPrivate)
|
||||
{
|
||||
if (Engine.Server.Config.AllowExternal != options.ListenPublic)
|
||||
{
|
||||
Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
|
||||
Engine.Server.Config.AllowExternal = options.ListenPublic;
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Unable to switch to public listening without admin rights.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Override port
|
||||
if (options.Port != 0)
|
||||
{
|
||||
if (Engine.Server.Config.Port != options.Port)
|
||||
{
|
||||
Engine.Logger.Info("Overriding port to " + options.Port);
|
||||
Engine.Server.Config.Port = options.Port;
|
||||
if (System.Environment.OSVersion.Platform != PlatformID.Unix)
|
||||
{
|
||||
if (ServerUtil.IsUserAdministrator())
|
||||
{
|
||||
Engine.Server.ReserveUrls(doInstall: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Unable to switch ports when not running as administrator");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Server.Initalize();
|
||||
Engine.Server.Start();
|
||||
Engine.RunTime.Spin();
|
||||
Engine.Logger.Info("Server thread exit");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e, "Top level exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -39,6 +39,10 @@
|
||||
<ApplicationIcon>jackett.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="CommandLine, Version=1.9.71.2, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@@ -1,149 +1,150 @@
|
||||
using CommandLine;
|
||||
using Jackett.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Updater
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
new Program().Run(args);
|
||||
}
|
||||
|
||||
private void Run(string[] args)
|
||||
{
|
||||
Engine.Logger.Info("Jackett Updater v" + GetCurrentVersion());
|
||||
|
||||
try {
|
||||
var options = new UpdaterConsoleOptions();
|
||||
if (Parser.Default.ParseArguments(args, options))
|
||||
{
|
||||
ProcessUpdate(options);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Failed to process update arguments!: " + string.Join(" ", args));
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e, "Exception applying update!");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCurrentVersion()
|
||||
{
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
return fvi.FileVersion;
|
||||
}
|
||||
|
||||
private void ProcessUpdate(UpdaterConsoleOptions options)
|
||||
{
|
||||
var updateLocation = GetUpdateLocation();
|
||||
if(!(updateLocation.EndsWith("\\") || updateLocation.EndsWith("/")))
|
||||
{
|
||||
updateLocation += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
var isWindows = System.Environment.OSVersion.Platform != PlatformID.Unix;
|
||||
var trayRunning = false;
|
||||
var trayProcesses = Process.GetProcessesByName("JackettTray");
|
||||
if (isWindows)
|
||||
{
|
||||
if (trayProcesses.Count() > 0)
|
||||
{
|
||||
foreach (var proc in trayProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
Engine.Logger.Info("Killing tray process " + proc.Id);
|
||||
proc.Kill();
|
||||
trayRunning = true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Logger.Info("Waiting for Jackett to close..");
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var files = Directory.GetFiles(updateLocation, "*.*", SearchOption.AllDirectories);
|
||||
foreach(var file in files)
|
||||
{
|
||||
var fileName = Path.GetFileName(file).ToLowerInvariant();
|
||||
|
||||
if (fileName.EndsWith(".zip") ||
|
||||
fileName.EndsWith(".tar") ||
|
||||
fileName.EndsWith(".gz"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Engine.Logger.Info("Copying " + fileName);
|
||||
var dest = Path.Combine(options.Path, file.Substring(updateLocation.Length));
|
||||
File.Copy(file, dest, true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (trayRunning)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo()
|
||||
{
|
||||
Arguments = options.Args,
|
||||
FileName = Path.Combine(options.Path, "JackettTray.exe"),
|
||||
UseShellExecute = true
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
if(string.Equals(options.Type, "JackettService.exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var serviceHelper = new ServiceConfigService(null, null);
|
||||
if (serviceHelper.ServiceExists())
|
||||
{
|
||||
serviceHelper.Start();
|
||||
}
|
||||
} else
|
||||
{
|
||||
var startInfo = new ProcessStartInfo()
|
||||
{
|
||||
Arguments = options.Args,
|
||||
FileName = Path.Combine(options.Path, "JackettConsole.exe"),
|
||||
UseShellExecute = true
|
||||
};
|
||||
|
||||
if (!isWindows)
|
||||
{
|
||||
startInfo.Arguments = startInfo.FileName + " " + startInfo.Arguments;
|
||||
startInfo.FileName = "mono";
|
||||
}
|
||||
|
||||
Engine.Logger.Info("Starting Jackett: " + startInfo.FileName + " " + startInfo.Arguments);
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUpdateLocation()
|
||||
{
|
||||
var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase);
|
||||
return new FileInfo(location.AbsolutePath).DirectoryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
using CommandLine;
|
||||
using Jackett.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Updater
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
new Program().Run(args);
|
||||
}
|
||||
|
||||
private void Run(string[] args)
|
||||
{
|
||||
Engine.SetupLogging(null, "updater.txt");
|
||||
Engine.Logger.Info("Jackett Updater v" + GetCurrentVersion());
|
||||
Engine.Logger.Info("Options " + string.Join(" ", args));
|
||||
try {
|
||||
var options = new UpdaterConsoleOptions();
|
||||
if (Parser.Default.ParseArguments(args, options))
|
||||
{
|
||||
ProcessUpdate(options);
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.Logger.Error("Failed to process update arguments!: " + string.Join(" ", args));
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e, "Exception applying update!");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCurrentVersion()
|
||||
{
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
return fvi.FileVersion;
|
||||
}
|
||||
|
||||
private void ProcessUpdate(UpdaterConsoleOptions options)
|
||||
{
|
||||
var updateLocation = GetUpdateLocation();
|
||||
if(!(updateLocation.EndsWith("\\") || updateLocation.EndsWith("/")))
|
||||
{
|
||||
updateLocation += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
var isWindows = System.Environment.OSVersion.Platform != PlatformID.Unix;
|
||||
var trayRunning = false;
|
||||
var trayProcesses = Process.GetProcessesByName("JackettTray");
|
||||
if (isWindows)
|
||||
{
|
||||
if (trayProcesses.Count() > 0)
|
||||
{
|
||||
foreach (var proc in trayProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
Engine.Logger.Info("Killing tray process " + proc.Id);
|
||||
proc.Kill();
|
||||
trayRunning = true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.Logger.Info("Waiting for Jackett to close..");
|
||||
Thread.Sleep(2000);
|
||||
Engine.Logger.Info("Finding files in: " + updateLocation);
|
||||
var files = Directory.GetFiles(updateLocation, "*.*", SearchOption.AllDirectories);
|
||||
foreach(var file in files)
|
||||
{
|
||||
var fileName = Path.GetFileName(file).ToLowerInvariant();
|
||||
|
||||
if (fileName.EndsWith(".zip") ||
|
||||
fileName.EndsWith(".tar") ||
|
||||
fileName.EndsWith(".gz"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Engine.Logger.Info("Copying " + fileName);
|
||||
var dest = Path.Combine(options.Path, file.Substring(updateLocation.Length));
|
||||
File.Copy(file, dest, true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Engine.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (trayRunning)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo()
|
||||
{
|
||||
Arguments = options.Args,
|
||||
FileName = Path.Combine(options.Path, "JackettTray.exe"),
|
||||
UseShellExecute = true
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
if(string.Equals(options.Type, "JackettService.exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var serviceHelper = new ServiceConfigService(null, null);
|
||||
if (serviceHelper.ServiceExists())
|
||||
{
|
||||
serviceHelper.Start();
|
||||
}
|
||||
} else
|
||||
{
|
||||
var startInfo = new ProcessStartInfo()
|
||||
{
|
||||
Arguments = options.Args,
|
||||
FileName = Path.Combine(options.Path, "JackettConsole.exe"),
|
||||
UseShellExecute = true
|
||||
};
|
||||
|
||||
if (!isWindows)
|
||||
{
|
||||
startInfo.Arguments = startInfo.FileName + " " + startInfo.Arguments;
|
||||
startInfo.FileName = "mono";
|
||||
}
|
||||
|
||||
Engine.Logger.Info("Starting Jackett: " + startInfo.FileName + " " + startInfo.Arguments);
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUpdateLocation()
|
||||
{
|
||||
var location = new Uri(Assembly.GetEntryAssembly().GetName().CodeBase);
|
||||
return new FileInfo(location.AbsolutePath).DirectoryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net45" />
|
||||
<package id="CommandLineParser" version="1.9.71" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
|
||||
|
@@ -42,14 +42,17 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#unconfigured-indexers .card {
|
||||
width: 200px;
|
||||
}
|
||||
#unconfigured-indexers .card {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.unconfigured-indexer {
|
||||
height: 120px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.indexer {
|
||||
height: 252px;
|
||||
}
|
||||
@@ -62,16 +65,20 @@
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.indexer-logo > .hidden-name {
|
||||
position: absolute;
|
||||
color: rgba(255, 255, 255, 0);
|
||||
left: 0;
|
||||
}
|
||||
.indexer-logo > .hidden-name {
|
||||
position: absolute;
|
||||
color: rgba(255, 255, 255, 0);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.indexer-logo > img {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #FFF;
|
||||
}
|
||||
.indexer-logo img {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #FFF;
|
||||
}
|
||||
|
||||
#unconfigured-indexers .indexer-logo img {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.indexer-name > h3 {
|
||||
margin-top: 13px;
|
||||
@@ -82,10 +89,9 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.indexer-buttons > .btn {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.indexer-buttons > .btn {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.indexer-button-test {
|
||||
width: 60px;
|
||||
|
@@ -1,15 +1,17 @@
|
||||
$(document).ready(function () {
|
||||
var basePath = '';
|
||||
|
||||
$(document).ready(function () {
|
||||
$.ajaxSetup({ cache: false });
|
||||
window.jackettIsLocal = window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === '127.0.0.1';
|
||||
|
||||
bindUIButtons();
|
||||
reloadIndexers();
|
||||
loadJackettSettings();
|
||||
|
||||
});
|
||||
|
||||
function getJackettConfig(callback) {
|
||||
var jqxhr = $.get("/admin/get_jackett_config", function (data) {
|
||||
var jqxhr = $.get("get_jackett_config", function (data) {
|
||||
|
||||
callback(data);
|
||||
}).fail(function () {
|
||||
@@ -22,6 +24,12 @@ function loadJackettSettings() {
|
||||
$("#api-key-input").val(data.config.api_key);
|
||||
$("#app-version").html(data.app_version);
|
||||
$("#jackett-port").val(data.config.port);
|
||||
$("#jackett-basepathoverride").val(data.config.basepathoverride);
|
||||
basePath = data.config.basepathoverride;
|
||||
if (basePath === null || basePath === undefined) {
|
||||
basePath = '';
|
||||
}
|
||||
|
||||
$("#jackett-savedir").val(data.config.blackholedir);
|
||||
$("#jackett-allowext").attr('checked', data.config.external);
|
||||
$("#jackett-allowupdate").attr('checked', data.config.updatedisabled);
|
||||
@@ -32,6 +40,8 @@ function loadJackettSettings() {
|
||||
if (password != null && password != '') {
|
||||
$("#logoutBtn").show();
|
||||
}
|
||||
|
||||
reloadIndexers();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,7 +49,7 @@ function reloadIndexers() {
|
||||
$('#indexers').hide();
|
||||
$('#indexers > .indexer').remove();
|
||||
$('#unconfigured-indexers').empty();
|
||||
var jqxhr = $.get("/admin/get_indexers", function (data) {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
displayIndexers(data.items);
|
||||
}).fail(function () {
|
||||
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
@@ -52,8 +62,8 @@ function displayIndexers(items) {
|
||||
$('#unconfigured-indexers-template').empty();
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
item.torznab_host = resolveUrl("/torznab/" + item.id);
|
||||
item.potato_host = resolveUrl("/potato/" + item.id);
|
||||
item.torznab_host = resolveUrl(basePath + "/torznab/" + item.id);
|
||||
item.potato_host = resolveUrl(basePath + "/potato/" + item.id);
|
||||
if (item.configured)
|
||||
$('#indexers').append(indexerTemplate(item));
|
||||
else
|
||||
@@ -72,9 +82,10 @@ function displayIndexers(items) {
|
||||
$('.indexer-setup').each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
var link = $btn.data("link");
|
||||
$btn.click(function () {
|
||||
$('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) {
|
||||
displayIndexerSetup(id);
|
||||
displayIndexerSetup(id, link);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -91,7 +102,7 @@ function prepareDeleteButtons() {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
@@ -111,8 +122,9 @@ function prepareSetupButtons() {
|
||||
$('.indexer-setup').each(function (i, btn) {
|
||||
var $btn = $(btn);
|
||||
var id = $btn.data("id");
|
||||
var link = $btn.data("link");
|
||||
$btn.click(function () {
|
||||
displayIndexerSetup(id);
|
||||
displayIndexerSetup(id, link);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -123,7 +135,7 @@ function prepareTestButtons() {
|
||||
var id = $btn.data("id");
|
||||
$btn.click(function () {
|
||||
doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer");
|
||||
var jqxhr = $.post("/admin/test_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
}
|
||||
@@ -137,15 +149,15 @@ function prepareTestButtons() {
|
||||
});
|
||||
}
|
||||
|
||||
function displayIndexerSetup(id) {
|
||||
function displayIndexerSetup(id, link) {
|
||||
|
||||
var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
}
|
||||
|
||||
populateSetupForm(id, data.name, data.config, data.caps);
|
||||
populateSetupForm(id, data.name, data.config, data.caps, link);
|
||||
|
||||
}).fail(function () {
|
||||
doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
|
||||
@@ -196,9 +208,9 @@ function populateConfigItems(configForm, config) {
|
||||
}
|
||||
}
|
||||
|
||||
function newConfigModal(title, config, caps) {
|
||||
function newConfigModal(title, config, caps, link) {
|
||||
var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html());
|
||||
var configForm = $(configTemplate({ title: title, caps: caps }));
|
||||
var configForm = $(configTemplate({ title: title, caps: caps, link:link }));
|
||||
$("#modals").append(configForm);
|
||||
populateConfigItems(configForm, config);
|
||||
return configForm;
|
||||
@@ -234,8 +246,8 @@ function getConfigModalJson(configForm) {
|
||||
return configJson;
|
||||
}
|
||||
|
||||
function populateSetupForm(indexerId, name, config, caps) {
|
||||
var configForm = newConfigModal(name, config, caps);
|
||||
function populateSetupForm(indexerId, name, config, caps, link) {
|
||||
var configForm = newConfigModal(name, config, caps, link);
|
||||
var $goButton = configForm.find(".setup-indexer-go");
|
||||
$goButton.click(function () {
|
||||
var data = { indexer: indexerId, name: name };
|
||||
@@ -245,7 +257,7 @@ function populateSetupForm(indexerId, name, config, caps) {
|
||||
$goButton.prop('disabled', true);
|
||||
$goButton.html($('#spinner').html());
|
||||
|
||||
var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) {
|
||||
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
|
||||
if (data.result == "error") {
|
||||
if (data.config) {
|
||||
populateConfigItems(configForm, data.config);
|
||||
@@ -319,7 +331,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#jackett-show-releases").click(function () {
|
||||
var jqxhr = $.get("/admin/GetCache", function (data) {
|
||||
var jqxhr = $.get("GetCache", function (data) {
|
||||
var releaseTemplate = Handlebars.compile($("#jackett-releases").html());
|
||||
var item = { releases: data, Title: 'Releases' };
|
||||
var releaseDialog = $(releaseTemplate(item));
|
||||
@@ -401,7 +413,7 @@ function bindUIButtons() {
|
||||
|
||||
$("#jackett-show-search").click(function () {
|
||||
$('#select-indexer-modal').remove();
|
||||
var jqxhr = $.get("/admin/get_indexers", function (data) {
|
||||
var jqxhr = $.get("get_indexers", function (data) {
|
||||
var scope = {
|
||||
items: data.items
|
||||
};
|
||||
@@ -457,7 +469,7 @@ function bindUIButtons() {
|
||||
$('#searchResults').empty();
|
||||
|
||||
$('#jackett-search-perform').html($('#spinner').html());
|
||||
var jqxhr = $.post("/admin/search", queryObj, function (data) {
|
||||
var jqxhr = $.post("search", queryObj, function (data) {
|
||||
$('#jackett-search-perform').html('Search trackers');
|
||||
var resultsTemplate = Handlebars.compile($("#jackett-search-results").html());
|
||||
var results = $('#searchResults');
|
||||
@@ -532,7 +544,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#view-jackett-logs").click(function () {
|
||||
var jqxhr = $.get("/admin/GetLogs", function (data) {
|
||||
var jqxhr = $.get("GetLogs", function (data) {
|
||||
var releaseTemplate = Handlebars.compile($("#jackett-logs").html());
|
||||
var item = { logs: data };
|
||||
var releaseDialog = $(releaseTemplate(item));
|
||||
@@ -546,6 +558,7 @@ function bindUIButtons() {
|
||||
|
||||
$("#change-jackett-port").click(function () {
|
||||
var jackett_port = $("#jackett-port").val();
|
||||
var jackett_basepathoverride = $("#jackett-basepathoverride").val();
|
||||
var jackett_external = $("#jackett-allowext").is(':checked');
|
||||
var jackett_update = $("#jackett-allowupdate").is(':checked');
|
||||
var jackett_prerelease = $("#jackett-prerelease").is(':checked');
|
||||
@@ -556,21 +569,17 @@ function bindUIButtons() {
|
||||
updatedisabled: jackett_update,
|
||||
prerelease: jackett_prerelease,
|
||||
blackholedir: $("#jackett-savedir").val(),
|
||||
logging: jackett_logging
|
||||
logging: jackett_logging,
|
||||
basepathoverride: jackett_basepathoverride
|
||||
};
|
||||
var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) {
|
||||
var jqxhr = $.post("set_config", JSON.stringify(jsonObject), function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
} else {
|
||||
doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok");
|
||||
window.setTimeout(function () {
|
||||
url = window.location.href;
|
||||
if (data.external) {
|
||||
window.location.href = url.substr(0, url.lastIndexOf(":") + 1) + data.port;
|
||||
} else {
|
||||
window.location.href = 'http://127.0.0.1:' + data.port;
|
||||
}
|
||||
window.location.reload(true);
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
@@ -580,7 +589,7 @@ function bindUIButtons() {
|
||||
});
|
||||
|
||||
$("#trigger-updater").click(function () {
|
||||
var jqxhr = $.get("/admin/trigger_update", function (data) {
|
||||
var jqxhr = $.get("trigger_update", function (data) {
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
return;
|
||||
@@ -596,7 +605,7 @@ function bindUIButtons() {
|
||||
var password = $("#jackett-adminpwd").val();
|
||||
var jsonObject = { password: password };
|
||||
|
||||
var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) {
|
||||
var jqxhr = $.post("set_admin_password", JSON.stringify(jsonObject), function (data) {
|
||||
|
||||
if (data.result == "error") {
|
||||
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
|
||||
|
@@ -4,27 +4,27 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
<script src="/libs/filesize.min.js"></script>
|
||||
<script src="/libs/jquery.min.js"></script>
|
||||
<script src="/libs/jquery.dataTables.min.js"></script>
|
||||
<script src="/libs/handlebars.min.js"></script>
|
||||
<script src="/libs/moment.min.js"></script>
|
||||
<script src="/libs/handlebarsmoment.js"></script>
|
||||
<script src="/bootstrap/bootstrap.min.js"></script>
|
||||
<script src="/libs/bootstrap-notify.js"></script>
|
||||
<script src="../libs/filesize.min.js"></script>
|
||||
<script src="../libs/jquery.min.js"></script>
|
||||
<script src="../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../libs/handlebars.min.js"></script>
|
||||
<script src="../libs/moment.min.js"></script>
|
||||
<script src="../libs/handlebarsmoment.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../libs/bootstrap-notify.js"></script>
|
||||
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
|
||||
|
||||
<link href="/bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/animate.css" rel="stylesheet">
|
||||
<link href="/custom.css" rel="stylesheet">
|
||||
<link href="/css/jquery.dataTables.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/font-awesome.min.css">
|
||||
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../animate.css" rel="stylesheet">
|
||||
<link href="../custom.css" rel="stylesheet">
|
||||
<link href="../css/jquery.dataTables.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../css/font-awesome.min.css">
|
||||
<title>Jackett</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="/jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
|
||||
<img id="logo" src="../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
|
||||
|
||||
<div class="pull-right jackett-apikey">
|
||||
<span class="input-header">API Key: </span>
|
||||
@@ -48,7 +48,7 @@
|
||||
<ol>
|
||||
<li>Go to <b>Settings > Indexers > Add > Torznab > Custom</b>.</li>
|
||||
<li>For <b>URL</b> enter the <b>Torznab Host</b> of one of the indexers.</li>
|
||||
<li>For the <b>API key</b> using the key below.</li>
|
||||
<li>For the <b>API key</b> using the key above.</li>
|
||||
</ol>
|
||||
<h4>Adding a Jackett indexer in CouchPotato</h4>
|
||||
<ol>
|
||||
@@ -84,10 +84,13 @@
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Base Path Override: </span>
|
||||
<input id="jackett-basepathoverride" class="form-control input-right" type="text" value="" placeholder="/jackett/">
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Server port: </span>
|
||||
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
|
||||
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<span class="input-header">Manual download blackhole directory: </span>
|
||||
@@ -172,10 +175,10 @@
|
||||
<div class="indexer-logo">
|
||||
<!-- Make section browser searchable -->
|
||||
<span class="hidden-name">{{name}}</span>
|
||||
<img alt="{{name}}" title="{{name}}" src="/logos/{{id}}.png" />
|
||||
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
|
||||
</div>
|
||||
<div class="indexer-buttons">
|
||||
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}">
|
||||
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm indexer-button-delete" data-id="{{id}}">
|
||||
@@ -203,14 +206,11 @@
|
||||
</script>
|
||||
<script id="unconfigured-indexer" type="text/x-handlebars-template">
|
||||
<div class="unconfigured-indexer card">
|
||||
<div class="indexer-logo">
|
||||
<div class="indexer-logo indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
|
||||
<!-- Make section browser searchable -->
|
||||
<span class="hidden-name">{{name}}</span>
|
||||
<img alt="{{name}}" title="{{name}}" src="/logos/{{id}}.png" />
|
||||
</div>
|
||||
<div class="indexer-buttons">
|
||||
<a class="btn btn-info" target="_blank" href="{{site_link}}">Visit <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span></a>
|
||||
<button class="indexer-setup btn btn-success" data-id="{{id}}">Setup <span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
||||
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
@@ -437,7 +437,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{title}}</h4>
|
||||
<h4 class="modal-title">{{title}} - <a target="_blank" href="{{link}}">{{link}}</a></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="config-setup-form"></form>
|
||||
@@ -481,6 +481,6 @@
|
||||
<span class="spinner glyphicon glyphicon-refresh"></span>
|
||||
</script>
|
||||
|
||||
<script src="/custom.js"></script>
|
||||
<script src="../custom.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -6,28 +6,28 @@
|
||||
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
|
||||
|
||||
<script src="/libs/jquery.min.js"></script>
|
||||
<script src="/libs/jquery.dataTables.min.js"></script>
|
||||
<script src="/libs/handlebars.min.js"></script>
|
||||
<script src="/libs/moment.min.js"></script>
|
||||
<script src="/libs/handlebarsmoment.js"></script>
|
||||
<script src="/bootstrap/bootstrap.min.js"></script>
|
||||
<script src="/libs/bootstrap-notify.js"></script>
|
||||
<script src="../libs/jquery.min.js"></script>
|
||||
<script src="../libs/jquery.dataTables.min.js"></script>
|
||||
<script src="../libs/handlebars.min.js"></script>
|
||||
<script src="../libs/moment.min.js"></script>
|
||||
<script src="../libs/handlebarsmoment.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script src="../libs/bootstrap-notify.js"></script>
|
||||
|
||||
<link href="/bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/animate.css" rel="stylesheet">
|
||||
<link href="/custom.css" rel="stylesheet">
|
||||
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../animate.css" rel="stylesheet">
|
||||
<link href="../custom.css" rel="stylesheet">
|
||||
|
||||
<title>Jackett</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<img id="logo" src="/jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
<img id="logo" src="../jacket_medium.png" /><span id="header-title">Jackett</span>
|
||||
|
||||
<hr />
|
||||
<h1>Login</h1>
|
||||
<form action="/Admin/Dashboard" method="post">
|
||||
<form action="Dashboard" method="post">
|
||||
<div class="input-area">
|
||||
<span class="input-header">Admin password</span>
|
||||
<input id="password" name="password" class="form-control input-right" type="password">
|
||||
|
BIN
src/Jackett/Content/logos/abnormal.png
Normal file
BIN
src/Jackett/Content/logos/abnormal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
src/Jackett/Content/logos/bitsoup.png
Normal file
BIN
src/Jackett/Content/logos/bitsoup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
BIN
src/Jackett/Content/logos/frenchadn.png
Normal file
BIN
src/Jackett/Content/logos/frenchadn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
src/Jackett/Content/logos/revolutiontt.png
Normal file
BIN
src/Jackett/Content/logos/revolutiontt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
BIN
src/Jackett/Content/logos/wihd.png
Normal file
BIN
src/Jackett/Content/logos/wihd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
src/Jackett/Content/logos/xspeeds.png
Normal file
BIN
src/Jackett/Content/logos/xspeeds.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
@@ -80,7 +80,7 @@ namespace Jackett.Controllers
|
||||
var ctx = Request.GetOwinContext();
|
||||
var authManager = ctx.Authentication;
|
||||
authManager.SignOut("ApplicationCookie");
|
||||
return Redirect("/Admin/Dashboard");
|
||||
return Redirect("Admin/Dashboard");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -318,6 +318,7 @@ namespace Jackett.Controllers
|
||||
cfg["prerelease"] = serverService.Config.UpdatePrerelease;
|
||||
cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10);
|
||||
cfg["logging"] = Startup.TracingEnabled;
|
||||
cfg["basepathoverride"] = serverService.Config.BasePathOverride;
|
||||
|
||||
|
||||
jsonReply["config"] = cfg;
|
||||
@@ -349,9 +350,12 @@ namespace Jackett.Controllers
|
||||
bool updateDisabled = (bool)postData["updatedisabled"];
|
||||
bool preRelease = (bool)postData["prerelease"];
|
||||
bool logging = (bool)postData["logging"];
|
||||
string basePathOverride = (string)postData["basepathoverride"];
|
||||
|
||||
Engine.Server.Config.UpdateDisabled = updateDisabled;
|
||||
Engine.Server.Config.UpdatePrerelease = preRelease;
|
||||
Engine.Server.Config.BasePathOverride = basePathOverride;
|
||||
Startup.BasePath = Engine.Server.BasePath();
|
||||
Engine.Server.SaveConfig();
|
||||
|
||||
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
|
||||
@@ -446,7 +450,7 @@ namespace Jackett.Controllers
|
||||
|
||||
private void ConfigureCacheResults(List<TrackerCacheResult> results)
|
||||
{
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
foreach (var result in results)
|
||||
{
|
||||
var link = result.Link;
|
||||
|
@@ -117,7 +117,7 @@ namespace Jackett.Controllers
|
||||
}
|
||||
|
||||
releases = indexer.FilterResults(torznabQuery, releases);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var potatoResponse = new TorrentPotatoResponse();
|
||||
|
||||
releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year);
|
||||
|
@@ -100,7 +100,7 @@ namespace Jackett.Controllers
|
||||
|
||||
logger.Info(logBuilder.ToString());
|
||||
|
||||
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
|
||||
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
|
||||
var resultPage = new ResultPage(new ChannelInfo
|
||||
{
|
||||
Title = indexer.DisplayName,
|
||||
|
@@ -85,6 +85,7 @@ namespace Jackett
|
||||
|
||||
using (var easy = new CurlEasy())
|
||||
{
|
||||
|
||||
easy.Url = curlRequest.Url;
|
||||
easy.BufferSize = 64 * 1024;
|
||||
easy.UserAgent = BrowserUtil.ChromeUserAgent;
|
||||
@@ -139,7 +140,12 @@ namespace Jackett
|
||||
{
|
||||
easy.SetOpt(CurlOption.SslVerifyhost, false);
|
||||
easy.SetOpt(CurlOption.SslVerifyPeer, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Startup.ProxyConnection != null)
|
||||
{
|
||||
easy.SetOpt(CurlOption.Proxy, Startup.ProxyConnection);
|
||||
}
|
||||
|
||||
easy.Perform();
|
||||
|
||||
@@ -155,6 +161,15 @@ namespace Jackett
|
||||
|
||||
var headerBytes = Combine(headerBuffers.ToArray());
|
||||
var headerString = Encoding.UTF8.GetString(headerBytes);
|
||||
if (Startup.ProxyConnection != null)
|
||||
{
|
||||
var firstcrlf = headerString.IndexOf("\r\n\r\n");
|
||||
var secondcrlf = headerString.IndexOf("\r\n\r\n", firstcrlf + 1);
|
||||
if (secondcrlf > 0)
|
||||
{
|
||||
headerString = headerString.Substring(firstcrlf + 4, secondcrlf - (firstcrlf));
|
||||
}
|
||||
}
|
||||
var headerParts = headerString.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var headers = new List<string[]>();
|
||||
var headerCount = 0;
|
||||
|
@@ -1,190 +1,191 @@
|
||||
using Autofac;
|
||||
using Jackett.Services;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.LayoutRenderers;
|
||||
using NLog.Targets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class Engine
|
||||
{
|
||||
private static IContainer container = null;
|
||||
|
||||
static Engine()
|
||||
{
|
||||
BuildContainer();
|
||||
|
||||
}
|
||||
|
||||
public static void BuildContainer()
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterModule<JackettModule>();
|
||||
container = builder.Build();
|
||||
|
||||
// Register the container in itself to allow for late resolves
|
||||
var secondaryBuilder = new ContainerBuilder();
|
||||
secondaryBuilder.RegisterInstance<IContainer>(container).SingleInstance();
|
||||
SetupLogging(secondaryBuilder);
|
||||
secondaryBuilder.Update(container);
|
||||
|
||||
}
|
||||
|
||||
public static IContainer GetContainer()
|
||||
{
|
||||
return container;
|
||||
}
|
||||
|
||||
public static bool IsWindows
|
||||
{
|
||||
get
|
||||
{
|
||||
return Environment.OSVersion.Platform == PlatformID.Win32NT;
|
||||
}
|
||||
}
|
||||
|
||||
public static IConfigurationService ConfigService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IConfigurationService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IProcessService ProcessService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IProcessService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IServiceConfigService ServiceConfig
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IServiceConfigService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static ITrayLockService LockService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<ITrayLockService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IServerService Server
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IServerService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IRunTimeService RunTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IRunTimeService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Logger Logger
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<Logger>();
|
||||
}
|
||||
}
|
||||
|
||||
public static ISecuityService SecurityService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<ISecuityService>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void SetupLogging(ContainerBuilder builder)
|
||||
{
|
||||
var logLevel = Startup.TracingEnabled ? LogLevel.Debug : LogLevel.Info;
|
||||
// Add custom date time format renderer as the default is too long
|
||||
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer));
|
||||
|
||||
var logConfig = new LoggingConfiguration();
|
||||
var logFile = new FileTarget();
|
||||
logConfig.AddTarget("file", logFile);
|
||||
logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
|
||||
logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), "log.txt");
|
||||
logFile.ArchiveFileName = "log.{#####}.txt";
|
||||
logFile.ArchiveAboveSize = 500000;
|
||||
logFile.MaxArchiveFiles = 5;
|
||||
logFile.KeepFileOpen = false;
|
||||
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
|
||||
var logFileRule = new LoggingRule("*", logLevel, logFile);
|
||||
logConfig.LoggingRules.Add(logFileRule);
|
||||
|
||||
var logConsole = new ColoredConsoleTarget();
|
||||
logConfig.AddTarget("console", logConsole);
|
||||
|
||||
logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}";
|
||||
var logConsoleRule = new LoggingRule("*", logLevel, logConsole);
|
||||
logConfig.LoggingRules.Add(logConsoleRule);
|
||||
|
||||
var logService = new LogCacheService();
|
||||
logConfig.AddTarget("service", logService);
|
||||
var serviceRule = new LoggingRule("*", logLevel, logService);
|
||||
logConfig.LoggingRules.Add(serviceRule);
|
||||
|
||||
LogManager.Configuration = logConfig;
|
||||
builder.RegisterInstance<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void SetLogLevel(LogLevel level)
|
||||
{
|
||||
|
||||
foreach (var rule in LogManager.Configuration.LoggingRules)
|
||||
{
|
||||
if (level == LogLevel.Debug)
|
||||
{
|
||||
if (!rule.Levels.Contains(LogLevel.Debug))
|
||||
{
|
||||
rule.EnableLoggingForLevel(LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rule.Levels.Contains(LogLevel.Debug))
|
||||
{
|
||||
rule.DisableLoggingForLevel(LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[LayoutRenderer("simpledatetime")]
|
||||
public class SimpleDateTimeRenderer : LayoutRenderer
|
||||
{
|
||||
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
|
||||
{
|
||||
builder.Append(DateTime.Now.ToString("MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
}
|
||||
using Autofac;
|
||||
using Jackett.Services;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.LayoutRenderers;
|
||||
using NLog.Targets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett
|
||||
{
|
||||
public class Engine
|
||||
{
|
||||
private static IContainer container = null;
|
||||
|
||||
static Engine()
|
||||
{
|
||||
BuildContainer();
|
||||
|
||||
}
|
||||
|
||||
public static void BuildContainer()
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterModule<JackettModule>();
|
||||
container = builder.Build();
|
||||
|
||||
// Register the container in itself to allow for late resolves
|
||||
var secondaryBuilder = new ContainerBuilder();
|
||||
secondaryBuilder.RegisterInstance<IContainer>(container).SingleInstance();
|
||||
SetupLogging(secondaryBuilder);
|
||||
secondaryBuilder.Update(container);
|
||||
|
||||
}
|
||||
|
||||
public static IContainer GetContainer()
|
||||
{
|
||||
return container;
|
||||
}
|
||||
|
||||
public static bool IsWindows
|
||||
{
|
||||
get
|
||||
{
|
||||
return Environment.OSVersion.Platform == PlatformID.Win32NT;
|
||||
}
|
||||
}
|
||||
|
||||
public static IConfigurationService ConfigService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IConfigurationService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IProcessService ProcessService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IProcessService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IServiceConfigService ServiceConfig
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IServiceConfigService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static ITrayLockService LockService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<ITrayLockService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IServerService Server
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IServerService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static IRunTimeService RunTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<IRunTimeService>();
|
||||
}
|
||||
}
|
||||
|
||||
public static Logger Logger
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<Logger>();
|
||||
}
|
||||
}
|
||||
|
||||
public static ISecuityService SecurityService
|
||||
{
|
||||
get
|
||||
{
|
||||
return container.Resolve<ISecuityService>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void SetupLogging(ContainerBuilder builder = null, string logfile = "log.txt")
|
||||
{
|
||||
var logLevel = Startup.TracingEnabled ? LogLevel.Debug : LogLevel.Info;
|
||||
// Add custom date time format renderer as the default is too long
|
||||
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer));
|
||||
|
||||
var logConfig = new LoggingConfiguration();
|
||||
var logFile = new FileTarget();
|
||||
logConfig.AddTarget("file", logFile);
|
||||
logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}";
|
||||
logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), logfile);
|
||||
logFile.ArchiveFileName = "log.{#####}.txt";
|
||||
logFile.ArchiveAboveSize = 500000;
|
||||
logFile.MaxArchiveFiles = 5;
|
||||
logFile.KeepFileOpen = false;
|
||||
logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
|
||||
var logFileRule = new LoggingRule("*", logLevel, logFile);
|
||||
logConfig.LoggingRules.Add(logFileRule);
|
||||
|
||||
var logConsole = new ColoredConsoleTarget();
|
||||
logConfig.AddTarget("console", logConsole);
|
||||
|
||||
logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}";
|
||||
var logConsoleRule = new LoggingRule("*", logLevel, logConsole);
|
||||
logConfig.LoggingRules.Add(logConsoleRule);
|
||||
|
||||
var logService = new LogCacheService();
|
||||
logConfig.AddTarget("service", logService);
|
||||
var serviceRule = new LoggingRule("*", logLevel, logService);
|
||||
logConfig.LoggingRules.Add(serviceRule);
|
||||
|
||||
LogManager.Configuration = logConfig;
|
||||
if (builder != null)
|
||||
{
|
||||
builder.RegisterInstance<Logger>(LogManager.GetCurrentClassLogger()).SingleInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetLogLevel(LogLevel level)
|
||||
{
|
||||
|
||||
foreach (var rule in LogManager.Configuration.LoggingRules)
|
||||
{
|
||||
if (level == LogLevel.Debug)
|
||||
{
|
||||
if (!rule.Levels.Contains(LogLevel.Debug))
|
||||
{
|
||||
rule.EnableLoggingForLevel(LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rule.Levels.Contains(LogLevel.Debug))
|
||||
{
|
||||
rule.DisableLoggingForLevel(LogLevel.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[LayoutRenderer("simpledatetime")]
|
||||
public class SimpleDateTimeRenderer : LayoutRenderer
|
||||
{
|
||||
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
|
||||
{
|
||||
builder.Append(DateTime.Now.ToString("MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
870
src/Jackett/Indexers/Abnormal.cs
Normal file
870
src/Jackett/Indexers/Abnormal.cs
Normal file
@@ -0,0 +1,870 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CsQuery;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig.Bespoke;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for Abnormal Private French Tracker
|
||||
/// </summary>
|
||||
public class Abnormal : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php"; } }
|
||||
private string TorrentCommentUrl { get { return TorrentDescriptionUrl; } }
|
||||
private string TorrentDescriptionUrl { get { return SiteLink + "torrents.php?id="; } }
|
||||
private string TorrentDownloadUrl { get { return SiteLink + "torrents.php?action=download&id={id}&authkey={auth_key}&torrent_pass={torrent_pass}"; } }
|
||||
private bool Latency { get { return ConfigData.Latency.Value; } }
|
||||
private bool DevMode { get { return ConfigData.DevMode.Value; } }
|
||||
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
|
||||
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
|
||||
|
||||
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
|
||||
private CQ fDom = null;
|
||||
|
||||
private ConfigurationDataAbnormal ConfigData
|
||||
{
|
||||
get { return (ConfigurationDataAbnormal)configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public Abnormal(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(
|
||||
name: "Abnormal",
|
||||
description: "General French Private Tracker",
|
||||
link: "https://abnormal.ws/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
downloadBase: "https://abnormal.ws/torrents.php?action=download&id=",
|
||||
configData: new ConfigurationDataAbnormal())
|
||||
{
|
||||
// Clean capabilities
|
||||
TorznabCaps.Categories.Clear();
|
||||
|
||||
// Movies
|
||||
AddCategoryMapping("MOVIE|DVDR", TorznabCatType.MoviesDVD); // DVDR
|
||||
AddCategoryMapping("MOVIE|DVDRIP", TorznabCatType.MoviesSD); // DVDRIP
|
||||
AddCategoryMapping("MOVIE|BDRIP", TorznabCatType.MoviesSD); // BDRIP
|
||||
AddCategoryMapping("MOVIE|VOSTFR", TorznabCatType.MoviesOther); // VOSTFR
|
||||
AddCategoryMapping("MOVIE|HD|720p", TorznabCatType.MoviesHD); // HD 720P
|
||||
AddCategoryMapping("MOVIE|HD|1080p", TorznabCatType.MoviesHD); // HD 1080P
|
||||
AddCategoryMapping("MOVIE|REMUXBR", TorznabCatType.MoviesBluRay); // REMUX BLURAY
|
||||
AddCategoryMapping("MOVIE|FULLBR", TorznabCatType.MoviesBluRay); // FULL BLURAY
|
||||
|
||||
// Series
|
||||
AddCategoryMapping("TV|SD|VOSTFR", TorznabCatType.TV); // SD VOSTFR
|
||||
AddCategoryMapping("TV|HD|VOSTFR", TorznabCatType.TVHD); // HD VOSTFR
|
||||
AddCategoryMapping("TV|SD|VF", TorznabCatType.TVSD); // SD VF
|
||||
AddCategoryMapping("TV|HD|VF", TorznabCatType.TVHD); // HD VF
|
||||
AddCategoryMapping("TV|PACK|FR", TorznabCatType.TVOTHER); // PACK FR
|
||||
AddCategoryMapping("TV|PACK|VOSTFR", TorznabCatType.TVOTHER); // PACK VOSTFR
|
||||
AddCategoryMapping("TV|EMISSIONS", TorznabCatType.TVOTHER); // EMISSIONS
|
||||
|
||||
// Anime
|
||||
AddCategoryMapping("ANIME", TorznabCatType.TVAnime); // ANIME
|
||||
|
||||
// Documentaries
|
||||
AddCategoryMapping("DOCS", TorznabCatType.TVDocumentary); // DOCS
|
||||
|
||||
// Music
|
||||
AddCategoryMapping("MUSIC|FLAC", TorznabCatType.AudioLossless); // FLAC
|
||||
AddCategoryMapping("MUSIC|MP3", TorznabCatType.AudioMP3); // MP3
|
||||
AddCategoryMapping("MUSIC|CONCERT", TorznabCatType.AudioVideo); // CONCERT
|
||||
|
||||
// Other
|
||||
AddCategoryMapping("PC|APP", TorznabCatType.PC); // PC
|
||||
AddCategoryMapping("PC|GAMES", TorznabCatType.PCGames); // GAMES
|
||||
AddCategoryMapping("EBOOKS", TorznabCatType.BooksEbook); // EBOOKS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure our WiHD Provider
|
||||
/// </summary>
|
||||
/// <param name="configJson">Our params in Json</param>
|
||||
/// <returns>Configuration state</returns>
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
// Retrieve config values set by Jackett's user
|
||||
ConfigData.LoadValuesFromJson(configJson);
|
||||
|
||||
// Check & Validate Config
|
||||
validateConfig();
|
||||
|
||||
// Setting our data for a better emulated browser (maximum security)
|
||||
// TODO: Encoded Content not supported by Jackett at this time
|
||||
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
// If we want to simulate a browser
|
||||
if (ConfigData.Browser.Value) {
|
||||
|
||||
// Clean headers
|
||||
emulatedBrowserHeaders.Clear();
|
||||
|
||||
// Inject headers
|
||||
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
|
||||
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
|
||||
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
|
||||
}
|
||||
|
||||
|
||||
// Getting login form to retrieve CSRF token
|
||||
var myRequest = new Utils.Clients.WebRequest()
|
||||
{
|
||||
Url = LoginUrl
|
||||
};
|
||||
|
||||
// Add our headers to request
|
||||
myRequest.Headers = emulatedBrowserHeaders;
|
||||
|
||||
// Building login form data
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", ConfigData.Username.Value },
|
||||
{ "password", ConfigData.Password.Value },
|
||||
{ "keeplogged", "1" },
|
||||
{ "login", "Connexion" }
|
||||
};
|
||||
|
||||
// Do the login
|
||||
var request = new Utils.Clients.WebRequest(){
|
||||
PostData = pairs,
|
||||
Referer = LoginUrl,
|
||||
Type = RequestType.POST,
|
||||
Url = LoginUrl,
|
||||
Headers = emulatedBrowserHeaders
|
||||
};
|
||||
|
||||
// Perform loggin
|
||||
latencyNow();
|
||||
output("\nPerform loggin.. with " + LoginUrl);
|
||||
var response = await webclient.GetString(request);
|
||||
|
||||
// Test if we are logged in
|
||||
await ConfigureIfOK(response.Cookies, response.Cookies.Contains("session="), () =>
|
||||
{
|
||||
// Parse error page
|
||||
CQ dom = response.Content;
|
||||
string message = dom[".warning"].Text().Split('.').Reverse().Skip(1).First();
|
||||
|
||||
// Try left
|
||||
string left = dom[".info"].Text().Trim();
|
||||
|
||||
// Oops, unable to login
|
||||
output("-> Login failed: \"" + message + "\" and " + left + " tries left before being banned for 6 hours !", "error");
|
||||
throw new ExceptionWithConfigData("Login failed: " + message, configData);
|
||||
});
|
||||
|
||||
output("-> Login Success");
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute our search query
|
||||
/// </summary>
|
||||
/// <param name="query">Query</param>
|
||||
/// <returns>Releases</returns>
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var torrentRowList = new List<CQ>();
|
||||
var searchTerm = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
int nbResults = 0;
|
||||
int pageLinkCount = 0;
|
||||
|
||||
// Check cache first so we don't query the server (if search term used or not in dev mode)
|
||||
if(!DevMode && !string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
lock (cache)
|
||||
{
|
||||
// Remove old cache items
|
||||
CleanCache();
|
||||
|
||||
// Search in cache
|
||||
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
|
||||
if (cachedResult != null)
|
||||
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// Build our query
|
||||
var request = buildQuery(searchTerm, query, searchUrl);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult results = await queryExec(request);
|
||||
fDom = results.Content;
|
||||
|
||||
try
|
||||
{
|
||||
// Find torrent rows
|
||||
var firstPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
|
||||
|
||||
// Check if there are pagination links at bottom
|
||||
Boolean pagination = (fDom[".linkbox > a"].Length != 0);
|
||||
|
||||
// If pagination available
|
||||
if (pagination) {
|
||||
// Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page)
|
||||
pageLinkCount = ParseUtil.CoerceInt(Regex.Match(fDom[".linkbox > a"].Last().Attr("href").ToString(), @"\d+").Value);
|
||||
|
||||
// Calculate average number of results (based on torrents rows lenght on first page)
|
||||
nbResults = firstPageRows.Count() * pageLinkCount;
|
||||
}
|
||||
else {
|
||||
// Check if we have a minimum of one result
|
||||
if (firstPageRows.Length >= 1)
|
||||
{
|
||||
// Retrieve total count on our alone page
|
||||
nbResults = firstPageRows.Count();
|
||||
pageLinkCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
output("\nNo result found for your query, please try another search term ...\n", "info");
|
||||
// No result found for this query
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
|
||||
output("\nThere are " + firstPageRows.Length + " results on the first page !");
|
||||
|
||||
// If we have a term used for search and pagination result superior to one
|
||||
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
|
||||
{
|
||||
// Starting with page #2
|
||||
for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
|
||||
{
|
||||
output("\nProcessing page #" + i);
|
||||
|
||||
// Request our page
|
||||
latencyNow();
|
||||
|
||||
// Build our query
|
||||
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult pageResults = await queryExec(pageRequest);
|
||||
|
||||
// Assign response
|
||||
fDom = pageResults.Content;
|
||||
|
||||
// Process page results
|
||||
var additionalPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No search term, maybe testing... so registring autkey and torrentpass for future uses
|
||||
string infosData = firstPageRows.First().Find("td:eq(3) > a").Attr("href");
|
||||
IList<string> infosList = infosData.Split('&').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
|
||||
IList<string> infosTracker = infosList.Select(s => s.Split(new[] { '=' }, 2)[1].Trim()).ToList();
|
||||
|
||||
output("\nStoring Authkey for future uses...");
|
||||
ConfigData.AuthKey.Value = infosTracker[2];
|
||||
|
||||
output("\nStoring TorrentPass for future uses...");
|
||||
ConfigData.TorrentPass.Value = infosTracker[3];
|
||||
|
||||
}
|
||||
|
||||
// Loop on results
|
||||
foreach (CQ tRow in torrentRowList)
|
||||
{
|
||||
output("\n=>> Torrent #" + (releases.Count + 1));
|
||||
|
||||
// ID
|
||||
int id = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1) > a").Attr("href").ToString(), @"\d+").Value);
|
||||
output("ID: " + id);
|
||||
|
||||
// Release Name
|
||||
string name = tRow.Find("td:eq(1) > a").Text().ToString();
|
||||
output("Release: " + name);
|
||||
|
||||
// Category
|
||||
string categoryID = tRow.Find("td:eq(0) > a").Attr("href").Replace("torrents.php?cat[]=", String.Empty);
|
||||
output("Category: " + MapTrackerCatToNewznab(categoryID) + " (" + categoryID + ")");
|
||||
|
||||
// Seeders
|
||||
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(6)").Text(), @"\d+").Value);
|
||||
output("Seeders: " + seeders);
|
||||
|
||||
// Leechers
|
||||
int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(7)").Text(), @"\d+").Value);
|
||||
output("Leechers: " + leechers);
|
||||
|
||||
// Completed
|
||||
int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5)").Text(), @"\d+").Value);
|
||||
output("Completed: " + completed);
|
||||
|
||||
// Size
|
||||
string sizeStr = tRow.Find("td:eq(4)").Text().Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb");
|
||||
long size = ReleaseInfo.GetBytes(sizeStr);
|
||||
output("Size: " + sizeStr + " (" + size + " bytes)");
|
||||
|
||||
// Publish DateToString
|
||||
IList<string> clockList = tRow.Find("td:eq(2) > span").Text().Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
|
||||
var date = agoToDate(clockList);
|
||||
output("Released on: " + date.ToLocalTime());
|
||||
|
||||
// Torrent Details URL
|
||||
Uri detailsLink = new Uri(TorrentDescriptionUrl + id);
|
||||
output("Details: " + detailsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Comments URL
|
||||
Uri commentsLink = new Uri(TorrentCommentUrl + id);
|
||||
output("Comments Link: " + commentsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Download URL
|
||||
Uri downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()).Replace("{auth_key}", ConfigData.AuthKey.Value).Replace("{torrent_pass}", ConfigData.TorrentPass.Value));
|
||||
output("Download Link: " + downloadLink.AbsoluteUri);
|
||||
|
||||
// Building release infos
|
||||
var release = new ReleaseInfo();
|
||||
release.Category = MapTrackerCatToNewznab(categoryID.ToString());
|
||||
release.Title = name;
|
||||
release.Seeders = seeders;
|
||||
release.Peers = seeders + leechers;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.PublishDate = date;
|
||||
release.Size = size;
|
||||
release.Guid = detailsLink;
|
||||
release.Comments = commentsLink;
|
||||
release.Link = downloadLink;
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
|
||||
}
|
||||
|
||||
// Return found releases
|
||||
return releases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build query to process
|
||||
/// </summary>
|
||||
/// <param name="term">Term to search</param>
|
||||
/// <param name="query">Torznab Query for categories mapping</param>
|
||||
/// <param name="url">Search url for provider</param>
|
||||
/// <param name="page">Page number to request</param>
|
||||
/// <returns>URL to query for parsing and processing results</returns>
|
||||
private string buildQuery(string term, TorznabQuery query, string url, int page = 0)
|
||||
{
|
||||
var parameters = new NameValueCollection();
|
||||
List<string> categoriesList = MapTorznabCapsToTrackers(query);
|
||||
string categories = null;
|
||||
|
||||
// Check if we are processing a new page
|
||||
if (page > 0)
|
||||
{
|
||||
// Adding page number to query
|
||||
parameters.Add("page", page.ToString());
|
||||
}
|
||||
|
||||
// Loop on Categories needed
|
||||
foreach (string category in categoriesList)
|
||||
{
|
||||
// If last, build !
|
||||
if (categoriesList.Last() == category)
|
||||
{
|
||||
// Adding previous categories to URL with latest category
|
||||
parameters.Add(Uri.EscapeDataString("cat[]"), HttpUtility.UrlEncode(category) + categories);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build categories parameter
|
||||
categories += "&" + Uri.EscapeDataString("cat[]") + "=" + HttpUtility.UrlEncode(category);
|
||||
}
|
||||
}
|
||||
|
||||
// If search term provided
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
// Add search term
|
||||
parameters.Add("search", HttpUtility.UrlEncode(term));
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add("search", HttpUtility.UrlEncode("%"));
|
||||
// Showing all torrents (just for output function)
|
||||
term = "all";
|
||||
}
|
||||
|
||||
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong cat[] param)
|
||||
url += "?" + string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
|
||||
|
||||
output("\nBuilded query for \"" + term + "\"... " + url);
|
||||
|
||||
// Return our search url
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switch Method for Querying
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryExec(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Switch in we are in DEV mode with Hard Drive Cache or not
|
||||
if (DevMode && CacheMode)
|
||||
{
|
||||
// Check Cache before querying and load previous results if available
|
||||
results = await queryCache(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Cache by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryCache(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Create Directory if not exist
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
|
||||
// Clean Storage Provider Directory from outdated cached queries
|
||||
cleanCacheStorage();
|
||||
|
||||
// Create fingerprint for request
|
||||
string file = directory + request.GetHashCode() + ".json";
|
||||
|
||||
// Checking modes states
|
||||
if (System.IO.File.Exists(file))
|
||||
{
|
||||
// File exist... loading it right now !
|
||||
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No cached file found, querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
|
||||
// Cached file didn't exist for our query, writing it right now !
|
||||
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Tracker by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryTracker(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Cache mode not enabled or cached file didn't exist for our query
|
||||
output("\nQuerying tracker for results....");
|
||||
|
||||
// Request our first page
|
||||
latencyNow();
|
||||
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
|
||||
|
||||
// Return results from tracker
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean Hard Drive Cache Storage
|
||||
/// </summary>
|
||||
/// <param name="force">Force Provider Folder deletion</param>
|
||||
private void cleanCacheStorage(Boolean force = false)
|
||||
{
|
||||
// Check cleaning method
|
||||
if(force)
|
||||
{
|
||||
// Deleting Provider Storage folder and all files recursively
|
||||
output("\nDeleting Provider Storage folder and all files recursively ...");
|
||||
|
||||
// Check if directory exist
|
||||
if(System.IO.Directory.Exists(directory))
|
||||
{
|
||||
// Delete storage directory of provider
|
||||
System.IO.Directory.Delete(directory, true);
|
||||
output("-> Storage folder deleted successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No directory, so nothing to do
|
||||
output("-> No Storage folder found for this provider !");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
// Check if there is file older than ... and delete them
|
||||
output("\nCleaning Provider Storage folder... in progress.");
|
||||
System.IO.Directory.GetFiles(directory)
|
||||
.Select(f => new System.IO.FileInfo(f))
|
||||
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
|
||||
.ToList()
|
||||
.ForEach(f => {
|
||||
output("Deleting cached file << " + f.Name + " >> ... done.");
|
||||
f.Delete();
|
||||
i++;
|
||||
});
|
||||
|
||||
// Inform on what was cleaned during process
|
||||
if(i > 0) {
|
||||
output("-> Deleted " + i + " cached files during cleaning.");
|
||||
}
|
||||
else {
|
||||
output("-> Nothing deleted during cleaning.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a random fake latency to avoid detection on tracker side
|
||||
/// </summary>
|
||||
private void latencyNow()
|
||||
{
|
||||
// Need latency ?
|
||||
if(Latency)
|
||||
{
|
||||
// Generate a random value in our range
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
|
||||
|
||||
// Sleep now...
|
||||
System.Threading.Thread.Sleep(waiting);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find torrent rows in search pages
|
||||
/// </summary>
|
||||
/// <returns>JQuery Object</returns>
|
||||
private CQ findTorrentRows()
|
||||
{
|
||||
// Return all occurencis of torrents found
|
||||
return fDom[".torrent_table > tbody > tr"].Not(".colhead");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert Ago date to DateTime
|
||||
/// </summary>
|
||||
/// <param name="clockList"></param>
|
||||
/// <returns>A DateTime</returns>
|
||||
private DateTime agoToDate(IList<string> clockList)
|
||||
{
|
||||
DateTime release = DateTime.Now;
|
||||
foreach (var ago in clockList)
|
||||
{
|
||||
// Check for years
|
||||
if (ago.Contains("années") || ago.Contains("année"))
|
||||
{
|
||||
// Number of years to remove
|
||||
int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddYears(-years);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for months
|
||||
else if (ago.Contains("mois"))
|
||||
{
|
||||
// Number of months to remove
|
||||
int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddMonths(-months);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for weeks
|
||||
else if (ago.Contains("semaines") || ago.Contains("semaine"))
|
||||
{
|
||||
// Number of weeks to remove
|
||||
int weeks = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddDays(-(7 * weeks));
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for days
|
||||
else if (ago.Contains("jours") || ago.Contains("jour"))
|
||||
{
|
||||
// Number of days to remove
|
||||
int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddDays(-days);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for hours
|
||||
else if (ago.Contains("heures") || ago.Contains("heure"))
|
||||
{
|
||||
// Number of hours to remove
|
||||
int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddHours(-hours);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for minutes
|
||||
else if (ago.Contains("mins") || ago.Contains("min"))
|
||||
{
|
||||
// Number of minutes to remove
|
||||
int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddMinutes(-minutes);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for seconds
|
||||
else if (ago.Contains("secondes") || ago.Contains("seconde"))
|
||||
{
|
||||
// Number of seconds to remove
|
||||
int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddSeconds(-seconds);
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Unable to detect release date of torrent", "error");
|
||||
//throw new Exception("Unable to detect release date of torrent");
|
||||
}
|
||||
}
|
||||
return release;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output message for logging or developpment (console)
|
||||
/// </summary>
|
||||
/// <param name="message">Message to output</param>
|
||||
/// <param name="level">Level for Logger</param>
|
||||
private void output(string message, string level = "debug")
|
||||
{
|
||||
// Check if we are in dev mode
|
||||
if(DevMode)
|
||||
{
|
||||
// Output message to console
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send message to logger with level
|
||||
switch (level)
|
||||
{
|
||||
default:
|
||||
goto case "debug";
|
||||
case "debug":
|
||||
// Only if Debug Level Enabled on Jackett
|
||||
if (Engine.Logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Debug(message);
|
||||
}
|
||||
break;
|
||||
case "info":
|
||||
logger.Info(message);
|
||||
break;
|
||||
case "error":
|
||||
logger.Error(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate Config entered by user on Jackett
|
||||
/// </summary>
|
||||
private void validateConfig()
|
||||
{
|
||||
output("\nValidating Settings ... \n");
|
||||
|
||||
// Check Username Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Username.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Password Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Password.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Max Page Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency Setting
|
||||
if (ConfigData.Latency.Value)
|
||||
{
|
||||
output("\nValidated Setting -- Latency Simulation enabled");
|
||||
|
||||
// Check Latency Start Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency End Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Browser Setting
|
||||
if (ConfigData.Browser.Value)
|
||||
{
|
||||
output("\nValidated Setting -- Browser Simulation enabled");
|
||||
|
||||
// Check ACCEPT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
|
||||
}
|
||||
|
||||
// Check ACCEPT-LANG header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
|
||||
}
|
||||
|
||||
// Check USER-AGENT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Check Dev Cache Settings
|
||||
if (ConfigData.HardDriveCache.Value == true)
|
||||
{
|
||||
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
|
||||
|
||||
// Check if Dev Mode enabled !
|
||||
if (!ConfigData.DevMode.Value)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Cache Keep Time Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete cache if previously existed
|
||||
cleanCacheStorage(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ namespace Jackett.Indexers
|
||||
private string LoginUrl { get { return SiteLink + "user/login"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrents.php?"; } }
|
||||
public bool AllowRaws { get { return configData.IncludeRaw.Value; } }
|
||||
public bool InsertSeason { get { return configData.InsertSeason!=null && configData.InsertSeason.Value; } }
|
||||
|
||||
new ConfigurationDataAnimeBytes configData
|
||||
{
|
||||
@@ -53,6 +54,13 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input)
|
||||
{
|
||||
// Prevent filtering
|
||||
return input;
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
@@ -119,12 +127,20 @@ namespace Jackett.Indexers
|
||||
base.LoadFromSavedConfiguration(jsonConfig);
|
||||
}
|
||||
|
||||
private string StripEpisodeNumber(string term)
|
||||
{
|
||||
// Tracer does not support searching with episode number so strip it if we have one
|
||||
term = Regex.Replace(term, @"\W(\dx)?\d?\d$", string.Empty);
|
||||
term = Regex.Replace(term, @"\W(S\d\d?E)?\d?\d$", string.Empty);
|
||||
return term;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
// The result list
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var result in await GetResults(query.SanitizedSearchTerm))
|
||||
foreach (var result in await GetResults(StripEpisodeNumber(query.SanitizedSearchTerm)))
|
||||
{
|
||||
releases.Add(result);
|
||||
}
|
||||
@@ -136,14 +152,6 @@ namespace Jackett.Indexers
|
||||
{
|
||||
var cleanSearchTerm = HttpUtility.UrlEncode(searchTerm);
|
||||
|
||||
// This tracker only deals with full seasons so chop off the episode/season number if we have it D:
|
||||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
var splitindex = searchTerm.LastIndexOf(' ');
|
||||
if (splitindex > -1)
|
||||
searchTerm = searchTerm.Substring(0, splitindex);
|
||||
}
|
||||
|
||||
// The result list
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
@@ -244,6 +252,12 @@ namespace Jackett.Indexers
|
||||
releaseInfo = releaseInfo.Replace("Episode ", "");
|
||||
releaseInfo = releaseInfo.Replace("Season ", "S");
|
||||
releaseInfo = releaseInfo.Trim();
|
||||
int test = 0;
|
||||
if (InsertSeason && int.TryParse(releaseInfo, out test) && releaseInfo.Length==1)
|
||||
{
|
||||
releaseInfo = "S01E0" + releaseInfo;
|
||||
}
|
||||
|
||||
}
|
||||
else if (rowCq.HasClass("torrent"))
|
||||
{
|
||||
|
@@ -147,37 +147,69 @@ namespace Jackett.Indexers
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
|
||||
protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
var byteResult = new WebClientByteResult();
|
||||
// Map to byte
|
||||
Mapper.Map(response, byteResult);
|
||||
await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies);
|
||||
await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
// Map to string
|
||||
Mapper.Map(byteResult, response);
|
||||
}
|
||||
|
||||
protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
|
||||
protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
// Follow up to 5 redirects
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (!response.IsRedirect)
|
||||
break;
|
||||
await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies);
|
||||
await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
|
||||
if (accumulateCookies)
|
||||
{
|
||||
CookieHeader = ResolveCookies((CookieHeader != null && CookieHeader != ""? CookieHeader + " " : "") + (overrideCookies != null && overrideCookies != "" ? overrideCookies + " " : "") + response.Cookies);
|
||||
overrideCookies = response.Cookies = CookieHeader;
|
||||
}
|
||||
if (overrideCookies != null && response.Cookies == null)
|
||||
{
|
||||
response.Cookies = overrideCookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
|
||||
private String ResolveCookies(String incomingCookies = "")
|
||||
{
|
||||
var redirRequestCookies = (CookieHeader != "" ? CookieHeader + " " : "") + incomingCookies;
|
||||
System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\s]+)=([^=]+)(?:\s|$)");
|
||||
Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
|
||||
var matches = expression.Match(redirRequestCookies);
|
||||
while (matches.Success)
|
||||
{
|
||||
if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
|
||||
matches = matches.NextMatch();
|
||||
}
|
||||
return string.Join(" ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
|
||||
|
||||
}
|
||||
|
||||
private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
|
||||
{
|
||||
if (incomingResponse.IsRedirect)
|
||||
{
|
||||
var redirRequestCookies = "";
|
||||
if (accumulateCookies)
|
||||
{
|
||||
redirRequestCookies = ResolveCookies((CookieHeader != "" ? CookieHeader + " " : "") + (overrideCookies != null ? overrideCookies : ""));
|
||||
} else
|
||||
{
|
||||
redirRequestCookies = (overrideCookies != null ? overrideCookies : "");
|
||||
}
|
||||
// Do redirect
|
||||
var redirectedResponse = await webclient.GetBytes(new WebRequest()
|
||||
{
|
||||
Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo,
|
||||
Referer = referrer,
|
||||
Cookies = overrideCookies ?? CookieHeader
|
||||
Cookies = redirRequestCookies
|
||||
});
|
||||
Mapper.Map(redirectedResponse, incomingResponse);
|
||||
}
|
||||
@@ -233,7 +265,7 @@ namespace Jackett.Indexers
|
||||
public async virtual Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var response = await RequestBytesWithCookiesAndRetry(link.ToString());
|
||||
if(response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
|
||||
if (response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
|
||||
{
|
||||
throw new Exception($"Remote server returned {response.Status.ToString()}");
|
||||
}
|
||||
@@ -269,7 +301,7 @@ namespace Jackett.Indexers
|
||||
Type = RequestType.GET,
|
||||
Cookies = CookieHeader,
|
||||
Referer = referer,
|
||||
Headers = headers
|
||||
Headers = headers
|
||||
};
|
||||
|
||||
if (cookieOverride != null)
|
||||
@@ -277,7 +309,7 @@ namespace Jackett.Indexers
|
||||
return await webclient.GetString(request);
|
||||
}
|
||||
|
||||
protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string,string> headers = null)
|
||||
protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string, string> headers = null)
|
||||
{
|
||||
Exception lastException = null;
|
||||
for (int i = 0; i < 3; i++)
|
||||
@@ -349,7 +381,7 @@ namespace Jackett.Indexers
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null)
|
||||
protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null, bool accumulateCookies = false)
|
||||
{
|
||||
var request = new Utils.Clients.WebRequest()
|
||||
{
|
||||
@@ -360,18 +392,22 @@ namespace Jackett.Indexers
|
||||
PostData = data
|
||||
};
|
||||
var response = await webclient.GetString(request);
|
||||
if (accumulateCookies)
|
||||
{
|
||||
response.Cookies = ResolveCookies((request.Cookies == null ? "" : request.Cookies + " ") + response.Cookies);
|
||||
}
|
||||
var firstCallCookies = response.Cookies;
|
||||
|
||||
if (response.IsRedirect)
|
||||
{
|
||||
await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies);
|
||||
await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies, accumulateCookies);
|
||||
}
|
||||
|
||||
if (returnCookiesFromFirstCall)
|
||||
{
|
||||
response.Cookies = firstCallCookies;
|
||||
response.Cookies = ResolveCookies(firstCallCookies + (accumulateCookies ? " " + response.Cookies : ""));
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
264
src/Jackett/Indexers/BitSoup.cs
Normal file
264
src/Jackett/Indexers/BitSoup.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using CsQuery;
|
||||
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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class BitSoup : BaseIndexer, IIndexer
|
||||
{
|
||||
private string UseLink { get { return (this.configData.AlternateLink.Value != null && this.configData.AlternateLink.Value != "" ? this.configData.AlternateLink.Value : SiteLink); } }
|
||||
private string BrowseUrl { get { return UseLink + "browse.php"; } }
|
||||
private string LoginUrl { get { return UseLink + "takelogin.php"; } }
|
||||
private string LoginReferer { get { return UseLink + "login.php"; } }
|
||||
private List<String> KnownURLs = new List<String>{ "https://www.bitsoup.me/","https://www.bitsoup.org/"};
|
||||
|
||||
new NxtGnConfigurationData configData
|
||||
{
|
||||
get { return (NxtGnConfigurationData)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public BitSoup(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "BitSoup",
|
||||
description: "SoupieBits",
|
||||
link: "https://www.bitsoup.me/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new NxtGnConfigurationData())
|
||||
{
|
||||
this.configData.DisplayText.Value = this.DisplayName + " has multiple URLs. The default (" + this.SiteLink + ") can be changed by entering a new value in the box below.";
|
||||
this.configData.DisplayText.Value += "The following are some known URLs for " + this.DisplayName;
|
||||
this.configData.DisplayText.Value += "<ul><li>" + String.Join("</li><li>", this.KnownURLs.ToArray()) + "</li></ul>";
|
||||
|
||||
//AddCategoryMapping("624", TorznabCatType.Console);
|
||||
//AddCategoryMapping("307", TorznabCatType.ConsoleNDS);
|
||||
//AddCategoryMapping("308", TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping("35", TorznabCatType.ConsoleWii);
|
||||
//AddCategoryMapping("309", TorznabCatType.ConsoleXbox);
|
||||
AddCategoryMapping("12", TorznabCatType.ConsoleXbox360);
|
||||
//AddCategoryMapping("305", TorznabCatType.ConsoleWiiwareVC);
|
||||
//AddCategoryMapping("309", TorznabCatType.ConsoleXBOX360DLC);
|
||||
AddCategoryMapping("38", TorznabCatType.ConsolePS3);
|
||||
//AddCategoryMapping("239", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("245", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("246", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("626", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("628", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("630", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("307", TorznabCatType.Console3DS);
|
||||
//AddCategoryMapping("308", TorznabCatType.ConsolePSVita);
|
||||
//AddCategoryMapping("307", TorznabCatType.ConsoleWiiU);
|
||||
//AddCategoryMapping("309", TorznabCatType.ConsoleXboxOne);
|
||||
//AddCategoryMapping("308", TorznabCatType.ConsolePS4);
|
||||
//AddCategoryMapping("631", TorznabCatType.Movies);
|
||||
//AddCategoryMapping("631", TorznabCatType.MoviesForeign);
|
||||
//AddCategoryMapping("455", TorznabCatType.MoviesOther);
|
||||
//AddCategoryMapping("633", TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping("19", TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping("41", TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping("17", TorznabCatType.Movies3D);
|
||||
AddCategoryMapping("80", TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping("20", TorznabCatType.MoviesDVD);
|
||||
//AddCategoryMapping("631", TorznabCatType.MoviesWEBDL);
|
||||
AddCategoryMapping("6", TorznabCatType.Audio);
|
||||
//AddCategoryMapping("623", TorznabCatType.AudioMP3);
|
||||
AddCategoryMapping("29", TorznabCatType.AudioVideo);
|
||||
//AddCategoryMapping("402", TorznabCatType.AudioVideo);
|
||||
AddCategoryMapping("5", TorznabCatType.AudioAudiobook);
|
||||
//AddCategoryMapping("1", TorznabCatType.AudioLossless);
|
||||
//AddCategoryMapping("403", TorznabCatType.AudioOther);
|
||||
//AddCategoryMapping("642", TorznabCatType.AudioOther);
|
||||
//AddCategoryMapping("1", TorznabCatType.AudioForeign);
|
||||
//AddCategoryMapping("233", TorznabCatType.PC);
|
||||
//AddCategoryMapping("236", TorznabCatType.PC);
|
||||
//AddCategoryMapping("1", TorznabCatType.PC0day);
|
||||
AddCategoryMapping("1", TorznabCatType.PCISO);
|
||||
//AddCategoryMapping("235", TorznabCatType.PCMac);
|
||||
//AddCategoryMapping("627", TorznabCatType.PCPhoneOther);
|
||||
AddCategoryMapping("21", TorznabCatType.PCGames);
|
||||
AddCategoryMapping("4", TorznabCatType.PCGames);
|
||||
//AddCategoryMapping("625", TorznabCatType.PCPhoneIOS);
|
||||
//AddCategoryMapping("625", TorznabCatType.PCPhoneAndroid);
|
||||
AddCategoryMapping("45", TorznabCatType.TV);
|
||||
//AddCategoryMapping("433", TorznabCatType.TV);
|
||||
//AddCategoryMapping("639", TorznabCatType.TVWEBDL);
|
||||
//AddCategoryMapping("433", TorznabCatType.TVWEBDL);
|
||||
//AddCategoryMapping("639", TorznabCatType.TVFOREIGN);
|
||||
//AddCategoryMapping("433", TorznabCatType.TVFOREIGN);
|
||||
AddCategoryMapping("7", TorznabCatType.TVSD);
|
||||
AddCategoryMapping("49", TorznabCatType.TVSD);
|
||||
AddCategoryMapping("42", TorznabCatType.TVHD);
|
||||
//AddCategoryMapping("433", TorznabCatType.TVHD);
|
||||
//AddCategoryMapping("635", TorznabCatType.TVOTHER);
|
||||
//AddCategoryMapping("636", TorznabCatType.TVSport);
|
||||
AddCategoryMapping("23", TorznabCatType.TVAnime);
|
||||
//AddCategoryMapping("634", TorznabCatType.TVDocumentary);
|
||||
AddCategoryMapping("9", TorznabCatType.XXX);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXDVD);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXWMV);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXXviD);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXx264);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXOther);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXImageset);
|
||||
//AddCategoryMapping("1", TorznabCatType.XXXPacks);
|
||||
//AddCategoryMapping("340", TorznabCatType.Other);
|
||||
//AddCategoryMapping("342", TorznabCatType.Other);
|
||||
//AddCategoryMapping("344", TorznabCatType.Other);
|
||||
//AddCategoryMapping("391", TorznabCatType.Other);
|
||||
//AddCategoryMapping("392", TorznabCatType.Other);
|
||||
//AddCategoryMapping("393", TorznabCatType.Other);
|
||||
//AddCategoryMapping("394", TorznabCatType.Other);
|
||||
//AddCategoryMapping("234", TorznabCatType.Other);
|
||||
//AddCategoryMapping("638", TorznabCatType.Other);
|
||||
//AddCategoryMapping("629", TorznabCatType.Other);
|
||||
//AddCategoryMapping("1", TorznabCatType.OtherMisc);
|
||||
//AddCategoryMapping("1", TorznabCatType.OtherHashed);
|
||||
//AddCategoryMapping("408", TorznabCatType.Books);
|
||||
AddCategoryMapping("24", TorznabCatType.BooksEbook);
|
||||
//AddCategoryMapping("406", TorznabCatType.BooksComics);
|
||||
//AddCategoryMapping("407", TorznabCatType.BooksComics);
|
||||
//AddCategoryMapping("409", TorznabCatType.BooksComics);
|
||||
//AddCategoryMapping("410", TorznabCatType.BooksMagazines);
|
||||
//AddCategoryMapping("1", TorznabCatType.BooksTechnical);
|
||||
//AddCategoryMapping("1", TorznabCatType.BooksOther);
|
||||
//AddCategoryMapping("1", TorznabCatType.BooksForeign);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
if (configData.AlternateLink.Value != null && configData.AlternateLink.Value != "")
|
||||
{
|
||||
if (!configData.AlternateLink.Value.EndsWith("/"))
|
||||
{
|
||||
configData.AlternateLink.Value = null;
|
||||
throw new Exception("AlternateLink must end with a slash.");
|
||||
}
|
||||
var match = Regex.Match(configData.AlternateLink.Value, "^https?:\\/\\/(?:[\\w]+\\.)+(?:[a-zA-Z]+)\\/$");
|
||||
if (!match.Success)
|
||||
{
|
||||
configData.AlternateLink.Value = null;
|
||||
throw new Exception("AlternateLink must be a valid url.");
|
||||
}
|
||||
}
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
|
||||
};
|
||||
|
||||
var loginPage = await RequestStringWithCookies(UseLink, string.Empty);
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginReferer, true);
|
||||
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var messageEl = dom["body > table.statusbar1 > tbody > tr > td > table > tbody > tr > td > table > tbody > tr > td"].First();
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
var searchUrl = BrowseUrl;
|
||||
var trackerCats = MapTorznabCapsToTrackers(query);
|
||||
var queryCollection = new NameValueCollection();
|
||||
|
||||
|
||||
queryCollection.Add("search", string.IsNullOrWhiteSpace(searchString)? "" : searchString);
|
||||
if (trackerCats.Count > 1)
|
||||
{
|
||||
for (var ct = 0; ct < trackerCats.Count; ct++) queryCollection.Add("cat" + (ct+1), trackerCats.ElementAt(ct));
|
||||
} else
|
||||
{
|
||||
queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
|
||||
}
|
||||
//queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
|
||||
searchUrl += "?" + queryCollection.GetQueryString();
|
||||
await ProcessPage(releases, searchUrl);
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl)
|
||||
{
|
||||
var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
|
||||
var results = response.Content;
|
||||
try
|
||||
{
|
||||
CQ dom = results;
|
||||
|
||||
var rows = dom["table.koptekst tr"];
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.Title = row.Cq().Find("td:eq(1) a").First().Text().Trim();
|
||||
release.Comments = new Uri(UseLink + row.Cq().Find("td:eq(1) a").First().Attr("href"));
|
||||
|
||||
release.Link = new Uri(UseLink + row.Cq().Find("td:eq(2) a").First().Attr("href"));
|
||||
release.Guid = release.Link;
|
||||
release.Description = release.Title;
|
||||
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
|
||||
var added = row.Cq().Find("td:eq(7)").First().Text().Trim();
|
||||
release.PublishDate = DateTime.ParseExact(added, "yyyy-MM-ddH:mm:ss", CultureInfo.InvariantCulture);
|
||||
|
||||
var sizeStr = row.Cq().Find("td:eq(8)").First().Text().Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(10)").First().Text().Trim());
|
||||
release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(11)").First().Text().Trim()) + release.Seeders;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public class NxtGnConfigurationData : ConfigurationData
|
||||
{
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public DisplayItem DisplayText { get; private set; }
|
||||
public StringItem AlternateLink { get; set; }
|
||||
|
||||
|
||||
public NxtGnConfigurationData()
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
DisplayText = new DisplayItem("") { Name = "" };
|
||||
AlternateLink = new StringItem { Name = "AlternateLinks" };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -254,4 +254,4 @@ namespace Jackett.Indexers
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ namespace Jackett.Indexers
|
||||
public class Demonoid : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "account_handler.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "files/?category=3&subcategory=All&quality=All&seeded=0&to=1&query={0}"; } }
|
||||
private string SearchUrl { get { return SiteLink + "files/?category={0}&subcategory=All&quality=All&seeded=0&to=1&query={1}"; } }
|
||||
|
||||
new ConfigurationDataBasicLogin configData
|
||||
{
|
||||
@@ -38,6 +38,10 @@ namespace Jackett.Indexers
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
{
|
||||
AddCategoryMapping(3, TorznabCatType.TV);
|
||||
AddCategoryMapping(3, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(3, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(1, TorznabCatType.Movies);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
@@ -64,9 +68,12 @@ namespace Jackett.Indexers
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString()));
|
||||
var trackerCats = MapTorznabCapsToTrackers(query);
|
||||
var cat = (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0");
|
||||
var episodeSearchUrl = string.Format(SearchUrl, cat, HttpUtility.UrlEncode(query.GetQueryString()));
|
||||
var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
|
||||
|
||||
|
||||
if (results.Content.Contains("No torrents found"))
|
||||
{
|
||||
return releases;
|
||||
|
966
src/Jackett/Indexers/FrenchADN.cs
Normal file
966
src/Jackett/Indexers/FrenchADN.cs
Normal file
@@ -0,0 +1,966 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CsQuery;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig.Bespoke;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for French-ADN Private Tracker
|
||||
/// </summary>
|
||||
public class FrenchADN : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login.php?"; } }
|
||||
private string LoginCheckUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
private string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string TorrentCommentUrl { get { return SiteLink + "details.php?id={id}#comments"; } }
|
||||
private string TorrentDescriptionUrl { get { return SiteLink + "details.php?id={id}"; } }
|
||||
private string TorrentDownloadUrl { get { return SiteLink + "download.php?id={id}"; } }
|
||||
private string TorrentThanksUrl { get { return SiteLink + "takethanks.php"; } }
|
||||
private bool Latency { get { return ConfigData.Latency.Value; } }
|
||||
private bool DevMode { get { return ConfigData.DevMode.Value; } }
|
||||
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
|
||||
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
|
||||
|
||||
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
|
||||
private CQ fDom = null;
|
||||
|
||||
private ConfigurationDataFrenchADN ConfigData
|
||||
{
|
||||
get { return (ConfigurationDataFrenchADN)configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public FrenchADN(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(
|
||||
name: "French-ADN",
|
||||
description: "Your French Family Provider",
|
||||
link: "https://french-adn.com/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
downloadBase: "https://french-adn.com/download.php?id=",
|
||||
configData: new ConfigurationDataFrenchADN())
|
||||
{
|
||||
// Clean capabilities
|
||||
TorznabCaps.Categories.Clear();
|
||||
|
||||
// Movies
|
||||
AddCategoryMapping("15", TorznabCatType.Movies); // ALL
|
||||
AddCategoryMapping("108", TorznabCatType.MoviesSD); // TS CAM
|
||||
AddCategoryMapping("25", TorznabCatType.MoviesSD); // BDRIP
|
||||
AddCategoryMapping("56", TorznabCatType.MoviesSD); // BRRIP
|
||||
AddCategoryMapping("16", TorznabCatType.MoviesSD); // DVDRIP
|
||||
AddCategoryMapping("49", TorznabCatType.MoviesDVD); // TVRIP
|
||||
AddCategoryMapping("102", TorznabCatType.MoviesWEBDL); // WEBRIP
|
||||
AddCategoryMapping("105", TorznabCatType.MoviesHD); // 1080P
|
||||
AddCategoryMapping("104", TorznabCatType.MoviesHD); // 720P
|
||||
AddCategoryMapping("17", TorznabCatType.MoviesDVD); // DVD R
|
||||
AddCategoryMapping("21", TorznabCatType.MoviesDVD); // DVD R5
|
||||
AddCategoryMapping("112", TorznabCatType.MoviesDVD); // DVD REMUX
|
||||
AddCategoryMapping("107", TorznabCatType.Movies3D); // 3D
|
||||
AddCategoryMapping("113", TorznabCatType.MoviesBluRay); // BLURAY
|
||||
AddCategoryMapping("118", TorznabCatType.MoviesHD); // MHD
|
||||
|
||||
// Series
|
||||
AddCategoryMapping("41", TorznabCatType.TV); // ALL
|
||||
AddCategoryMapping("43", TorznabCatType.TV); // VF
|
||||
AddCategoryMapping("44", TorznabCatType.TV); // VOSTFR
|
||||
AddCategoryMapping("42", TorznabCatType.TV); // PACK
|
||||
|
||||
// TV
|
||||
AddCategoryMapping("110", TorznabCatType.TV); // SHOWS
|
||||
|
||||
// Anime
|
||||
AddCategoryMapping("109", TorznabCatType.TVAnime); // ANIME
|
||||
|
||||
// Manga
|
||||
AddCategoryMapping("119", TorznabCatType.TVAnime); // MANGA
|
||||
|
||||
// Documentaries
|
||||
AddCategoryMapping("114", TorznabCatType.TVDocumentary); // DOCUMENTARY
|
||||
|
||||
// Music
|
||||
AddCategoryMapping("22", TorznabCatType.Audio); // ALL
|
||||
AddCategoryMapping("24", TorznabCatType.AudioLossless); // FLAC
|
||||
AddCategoryMapping("23", TorznabCatType.AudioMP3); // MP3
|
||||
|
||||
// Games
|
||||
AddCategoryMapping("33", TorznabCatType.PCGames); // ALL
|
||||
AddCategoryMapping("45", TorznabCatType.PCGames); // PC GAMES
|
||||
AddCategoryMapping("93", TorznabCatType.Console3DS); // 3DS
|
||||
AddCategoryMapping("94", TorznabCatType.Console); // PS2
|
||||
AddCategoryMapping("93", TorznabCatType.ConsolePS3); // PS3
|
||||
AddCategoryMapping("95", TorznabCatType.ConsolePSP); // PSP
|
||||
AddCategoryMapping("35", TorznabCatType.ConsolePS3); // WII
|
||||
|
||||
// Applications
|
||||
AddCategoryMapping("11", TorznabCatType.PC); // ALL
|
||||
AddCategoryMapping("12", TorznabCatType.PC); // APPS WINDOWS
|
||||
AddCategoryMapping("97", TorznabCatType.PCMac); // APPS MAC
|
||||
AddCategoryMapping("98", TorznabCatType.PC); // APPS LINUX
|
||||
|
||||
// Books
|
||||
AddCategoryMapping("115", TorznabCatType.BooksEbook); // EBOOK
|
||||
AddCategoryMapping("114", TorznabCatType.BooksComics); // COMICS
|
||||
|
||||
// Other
|
||||
AddCategoryMapping("103", TorznabCatType.Other); // STAFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure our FADN Provider
|
||||
/// </summary>
|
||||
/// <param name="configJson">Our params in Json</param>
|
||||
/// <returns>Configuration state</returns>
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
// Retrieve config values set by Jackett's user
|
||||
ConfigData.LoadValuesFromJson(configJson);
|
||||
|
||||
// Check & Validate Config
|
||||
validateConfig();
|
||||
|
||||
// Setting our data for a better emulated browser (maximum security)
|
||||
// TODO: Encoded Content not supported by Jackett at this time
|
||||
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
// If we want to simulate a browser
|
||||
if (ConfigData.Browser.Value)
|
||||
{
|
||||
|
||||
// Clean headers
|
||||
emulatedBrowserHeaders.Clear();
|
||||
|
||||
// Inject headers
|
||||
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
|
||||
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
|
||||
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
|
||||
}
|
||||
|
||||
// Build WebRequest for index
|
||||
var myIndexRequest = new WebRequest()
|
||||
{
|
||||
Type = RequestType.GET,
|
||||
Url = SiteLink,
|
||||
Headers = emulatedBrowserHeaders
|
||||
};
|
||||
|
||||
// Get index page for cookies
|
||||
output("\nGetting index page (for cookies).. with " + SiteLink);
|
||||
var indexPage = await webclient.GetString(myIndexRequest);
|
||||
|
||||
// Building login form data
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", ConfigData.Username.Value },
|
||||
{ "password", ConfigData.Password.Value }
|
||||
};
|
||||
|
||||
// Build WebRequest for login
|
||||
var myRequestLogin = new WebRequest()
|
||||
{
|
||||
Type = RequestType.GET,
|
||||
Url = LoginUrl,
|
||||
Headers = emulatedBrowserHeaders,
|
||||
Cookies = indexPage.Cookies,
|
||||
Referer = SiteLink
|
||||
};
|
||||
|
||||
// Get login page -- (not used, but simulation needed by tracker security's checks)
|
||||
latencyNow();
|
||||
output("\nGetting login page (user simulation).. with " + LoginUrl);
|
||||
var loginPage = await webclient.GetString(myRequestLogin);
|
||||
|
||||
// Build WebRequest for submitting authentification
|
||||
var request = new WebRequest()
|
||||
{
|
||||
PostData = pairs,
|
||||
Referer = LoginUrl,
|
||||
Type = RequestType.POST,
|
||||
Url = LoginCheckUrl,
|
||||
Headers = emulatedBrowserHeaders,
|
||||
Cookies = indexPage.Cookies,
|
||||
|
||||
};
|
||||
|
||||
// Perform loggin
|
||||
latencyNow();
|
||||
output("\nPerform loggin.. with " + LoginCheckUrl);
|
||||
var response = await webclient.GetString(request);
|
||||
|
||||
// Test if we are logged in
|
||||
await ConfigureIfOK(response.Cookies, !string.IsNullOrEmpty(response.Cookies) && !response.IsRedirect, () =>
|
||||
{
|
||||
// Default error message
|
||||
string message = "Error during attempt !";
|
||||
|
||||
// Parse redirect header
|
||||
string redirectTo = response.RedirectingTo;
|
||||
|
||||
// Analyzer error code
|
||||
if(redirectTo.Contains("login.php?error=4"))
|
||||
{
|
||||
// Set message
|
||||
message = "Wrong username or password !";
|
||||
}
|
||||
|
||||
// Oops, unable to login
|
||||
output("-> Login failed: " + message, "error");
|
||||
throw new ExceptionWithConfigData("Login failed: " + message, configData);
|
||||
});
|
||||
|
||||
output("\nCookies saved for future uses...");
|
||||
ConfigData.CookieHeader.Value = indexPage.Cookies + " " + response.Cookies + " ts_username=" + ConfigData.Username.Value;
|
||||
|
||||
output("\n-> Login Success\n");
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute our search query
|
||||
/// </summary>
|
||||
/// <param name="query">Query</param>
|
||||
/// <returns>Releases</returns>
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var torrentRowList = new List<CQ>();
|
||||
var searchTerm = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
int nbResults = 0;
|
||||
int pageLinkCount = 0;
|
||||
|
||||
// Check cache first so we don't query the server (if search term used or not in dev mode)
|
||||
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
lock (cache)
|
||||
{
|
||||
// Remove old cache items
|
||||
CleanCache();
|
||||
|
||||
// Search in cache
|
||||
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
|
||||
if (cachedResult != null)
|
||||
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// Build our query
|
||||
var request = buildQuery(searchTerm, query, searchUrl);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult results = await queryExec(request);
|
||||
fDom = results.Content;
|
||||
|
||||
try
|
||||
{
|
||||
// Find torrent rows
|
||||
var firstPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
|
||||
|
||||
// Check if there are pagination links at bottom
|
||||
Boolean pagination = (fDom["#quicknavpage_menu"].Length != 0);
|
||||
|
||||
// If pagination available
|
||||
if (pagination)
|
||||
{
|
||||
// Retrieve available pages (3 pages shown max)
|
||||
pageLinkCount = fDom["#navcontainer_f:first > ul"].Find("a").Not(".smalltext").Not("#quicknavpage").Length;
|
||||
|
||||
// Last button ? (So more than 3 page are available)
|
||||
Boolean more = (fDom["#navcontainer_f:first > ul"].Find("a.smalltext").Length > 1); ;
|
||||
|
||||
// More page than 3 pages ?
|
||||
if (more)
|
||||
{
|
||||
// Get total page count from last link
|
||||
pageLinkCount = ParseUtil.CoerceInt(Regex.Match(fDom["#navcontainer_f:first > ul"].Find("a:eq(4)").Attr("href").ToString(), @"\d+").Value);
|
||||
}
|
||||
|
||||
// Calculate average number of results (based on torrents rows lenght on first page)
|
||||
nbResults = firstPageRows.Count() * pageLinkCount;
|
||||
}
|
||||
else {
|
||||
nbResults = 1;
|
||||
pageLinkCount = 1;
|
||||
|
||||
// Check if we have a minimum of one result
|
||||
if (firstPageRows.Length > 1)
|
||||
{
|
||||
// Retrieve total count on our alone page
|
||||
nbResults = firstPageRows.Count();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if no result
|
||||
if(torrentRowList.First().Find("td").Length == 1)
|
||||
{
|
||||
// No results found
|
||||
output("\nNo result found for your query, please try another search term ...\n", "info");
|
||||
|
||||
// No result found for this query
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
||||
output("\nFound " + nbResults + " result(s) (+/- " + firstPageRows.Length + ") in " + pageLinkCount + " page(s) for this query !");
|
||||
output("\nThere are " + firstPageRows.Length + " results on the first page !");
|
||||
|
||||
// If we have a term used for search and pagination result superior to one
|
||||
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
|
||||
{
|
||||
// Starting with page #2
|
||||
for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
|
||||
{
|
||||
output("\nProcessing page #" + i);
|
||||
|
||||
// Request our page
|
||||
latencyNow();
|
||||
|
||||
// Build our query -- Minus 1 to page due to strange pagination number on tracker side, starting with page 0...
|
||||
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult pageResults = await queryExec(pageRequest);
|
||||
|
||||
// Assign response
|
||||
fDom = pageResults.Content;
|
||||
|
||||
// Process page results
|
||||
var additionalPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
|
||||
}
|
||||
}
|
||||
|
||||
// Loop on results
|
||||
foreach (CQ tRow in torrentRowList)
|
||||
{
|
||||
output("\n=>> Torrent #" + (releases.Count + 1));
|
||||
|
||||
// ID
|
||||
int id = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(1) > div:first > a").Attr("name").ToString(), @"\d+").Value);
|
||||
output("ID: " + id);
|
||||
|
||||
// Check if torrent is not nuked by tracker or rulez, can't download it
|
||||
if (tRow.Find("td:eq(2) > a").Length == 0)
|
||||
{
|
||||
// Next item
|
||||
output("Torrent is nuked, we can't download it, going to next torrent...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Release Name
|
||||
string name = tRow.Find("td:eq(2) > a").Attr("title").ToString().Substring(24).Trim();
|
||||
output("Release: " + name);
|
||||
|
||||
// Category
|
||||
int categoryID = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(0) > a").Attr("href").ToString(), @"\d+").Value);
|
||||
string categoryName = tRow.Find("td:eq(0) > a > img").Attr("title").Split(new[] { ':' }, 2)[1].Trim().ToString();
|
||||
output("Category: " + MapTrackerCatToNewznab(categoryID.ToString()) + " (" + categoryID + " - " + categoryName + ")");
|
||||
|
||||
// Seeders
|
||||
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(5) > div > font").Select(s => Regex.Replace(s.ToString(), "<.*?>", String.Empty)).ToString(), @"\d+").Value);
|
||||
output("Seeders: " + seeders);
|
||||
|
||||
// Leechers
|
||||
int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(6) > div > font").Text().ToString(), @"\d+").Value);
|
||||
output("Leechers: " + leechers);
|
||||
|
||||
// Completed
|
||||
int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(4)").Text().ToString(), @"\d+").Value);
|
||||
output("Completed: " + completed);
|
||||
|
||||
// Files
|
||||
int files = 1;
|
||||
if (tRow.Find("td:eq(3) > a").Length == 1)
|
||||
{
|
||||
files = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(3) > a").Text().ToString(), @"\d+").Value);
|
||||
}
|
||||
output("Files: " + files);
|
||||
|
||||
// Health
|
||||
int percent = ParseUtil.CoerceInt(Regex.Match(tRow.Find("td:eq(7) > img").Attr("src").ToString(), @"\d+").Value) * 10;
|
||||
output("Health: " + percent + "%");
|
||||
|
||||
// Size
|
||||
string humanSize = tRow.Find("td:eq(8)").Text().ToString().ToLowerInvariant();
|
||||
long size = ReleaseInfo.GetBytes(humanSize);
|
||||
output("Size: " + humanSize + " (" + size + " bytes)");
|
||||
|
||||
// Date & IMDB & Genre
|
||||
string infosData = tRow.Find("td:eq(1) > div:last").Text().ToString();
|
||||
IList<string> infosList = Regex.Split(infosData, "\\|").ToList();
|
||||
IList<string> infosTorrent = infosList.Select(s => s.Split(new[] { ':' }, 2)[1].Trim()).ToList();
|
||||
|
||||
// --> Date
|
||||
DateTime date = formatDate(infosTorrent.First());
|
||||
output("Released on: " + date.ToLocalTime().ToString());
|
||||
|
||||
// --> Genre
|
||||
string genre = infosTorrent.Last();
|
||||
output("Genre: " + genre);
|
||||
|
||||
// Torrent Details URL
|
||||
Uri detailsLink = new Uri(TorrentDescriptionUrl.Replace("{id}", id.ToString()));
|
||||
output("Details: " + detailsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Comments URL
|
||||
Uri commentsLink = new Uri(TorrentCommentUrl.Replace("{id}", id.ToString()));
|
||||
output("Comments Link: " + commentsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Download URL
|
||||
Uri downloadLink = new Uri(TorrentDownloadUrl.Replace("{id}", id.ToString()));
|
||||
output("Download Link: " + downloadLink.AbsoluteUri);
|
||||
|
||||
// Building release infos
|
||||
var release = new ReleaseInfo();
|
||||
release.Category = MapTrackerCatToNewznab(categoryID.ToString());
|
||||
release.Title = name;
|
||||
release.Seeders = seeders;
|
||||
release.Peers = seeders + leechers;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
release.PublishDate = date;
|
||||
release.Size = size;
|
||||
release.Guid = detailsLink;
|
||||
release.Comments = commentsLink;
|
||||
release.Link = downloadLink;
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
|
||||
}
|
||||
|
||||
// Return found releases
|
||||
return releases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build query to process
|
||||
/// </summary>
|
||||
/// <param name="term">Term to search</param>
|
||||
/// <param name="query">Torznab Query for categories mapping</param>
|
||||
/// <param name="url">Search url for provider</param>
|
||||
/// <param name="page">Page number to request</param>
|
||||
/// <returns>URL to query for parsing and processing results</returns>
|
||||
private string buildQuery(string term, TorznabQuery query, string url, int page = 0)
|
||||
{
|
||||
var parameters = new NameValueCollection();
|
||||
List<string> categoriesList = MapTorznabCapsToTrackers(query);
|
||||
|
||||
// Building our tracker query
|
||||
parameters.Add("do", "search");
|
||||
|
||||
// If search term provided
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
// Add search term ~~ Strange search engine, need to replace space with dot for results !
|
||||
parameters.Add("keywords", term.Replace(' ', '.'));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Showing all torrents (just for output function)
|
||||
parameters.Add("keywords", "");
|
||||
term = "all";
|
||||
}
|
||||
|
||||
// Adding requested categories
|
||||
if(categoriesList.Count > 0)
|
||||
{
|
||||
// Add categories
|
||||
parameters.Add("category", String.Join(",", categoriesList));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add empty category parameter
|
||||
parameters.Add("category", "");
|
||||
}
|
||||
|
||||
// Building our tracker query
|
||||
parameters.Add("search_type", "t_name");
|
||||
|
||||
// Check if we are processing a new page
|
||||
if (page > 1)
|
||||
{
|
||||
// Adding page number to query
|
||||
parameters.Add("page", page.ToString());
|
||||
}
|
||||
|
||||
// Building our query
|
||||
url += "?" + parameters.GetQueryString();
|
||||
|
||||
output("\nBuilded query for \"" + term + "\"... " + url);
|
||||
|
||||
// Return our search url
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switch Method for Querying
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryExec(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Switch in we are in DEV mode with Hard Drive Cache or not
|
||||
if (DevMode && CacheMode)
|
||||
{
|
||||
// Check Cache before querying and load previous results if available
|
||||
results = await queryCache(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Cache by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryCache(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Create Directory if not exist
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
|
||||
// Clean Storage Provider Directory from outdated cached queries
|
||||
cleanCacheStorage();
|
||||
|
||||
// Create fingerprint for request
|
||||
string file = directory + request.GetHashCode() + ".json";
|
||||
|
||||
// Checking modes states
|
||||
if (System.IO.File.Exists(file))
|
||||
{
|
||||
// File exist... loading it right now !
|
||||
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No cached file found, querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
|
||||
// Cached file didn't exist for our query, writing it right now !
|
||||
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Tracker by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryTracker(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Cache mode not enabled or cached file didn't exist for our query
|
||||
output("\nQuerying tracker for results....");
|
||||
|
||||
// Request our first page
|
||||
latencyNow();
|
||||
results = await RequestStringWithCookiesAndRetry(request, ConfigData.CookieHeader.Value, SearchUrl, emulatedBrowserHeaders);
|
||||
|
||||
// Return results from tracker
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean Hard Drive Cache Storage
|
||||
/// </summary>
|
||||
/// <param name="force">Force Provider Folder deletion</param>
|
||||
private void cleanCacheStorage(Boolean force = false)
|
||||
{
|
||||
// Check cleaning method
|
||||
if (force)
|
||||
{
|
||||
// Deleting Provider Storage folder and all files recursively
|
||||
output("\nDeleting Provider Storage folder and all files recursively ...");
|
||||
|
||||
// Check if directory exist
|
||||
if (System.IO.Directory.Exists(directory))
|
||||
{
|
||||
// Delete storage directory of provider
|
||||
System.IO.Directory.Delete(directory, true);
|
||||
output("-> Storage folder deleted successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No directory, so nothing to do
|
||||
output("-> No Storage folder found for this provider !");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
// Check if there is file older than ... and delete them
|
||||
output("\nCleaning Provider Storage folder... in progress.");
|
||||
System.IO.Directory.GetFiles(directory)
|
||||
.Select(f => new System.IO.FileInfo(f))
|
||||
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
|
||||
.ToList()
|
||||
.ForEach(f => {
|
||||
output("Deleting cached file << " + f.Name + " >> ... done.");
|
||||
f.Delete();
|
||||
i++;
|
||||
});
|
||||
|
||||
// Inform on what was cleaned during process
|
||||
if (i > 0)
|
||||
{
|
||||
output("-> Deleted " + i + " cached files during cleaning.");
|
||||
}
|
||||
else {
|
||||
output("-> Nothing deleted during cleaning.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a random fake latency to avoid detection on tracker side
|
||||
/// </summary>
|
||||
private void latencyNow()
|
||||
{
|
||||
// Need latency ?
|
||||
if (Latency)
|
||||
{
|
||||
// Generate a random value in our range
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
|
||||
|
||||
// Sleep now...
|
||||
System.Threading.Thread.Sleep(waiting);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find torrent rows in search pages
|
||||
/// </summary>
|
||||
/// <returns>JQuery Object</returns>
|
||||
private CQ findTorrentRows()
|
||||
{
|
||||
// Return all occurencis of torrents found
|
||||
return fDom["#showcontents > table > tbody > tr:not(:first)"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format Date to DateTime
|
||||
/// </summary>
|
||||
/// <param name="clock"></param>
|
||||
/// <returns>A DateTime</returns>
|
||||
private DateTime formatDate(string clock)
|
||||
{
|
||||
DateTime date;
|
||||
|
||||
// Switch from date format
|
||||
if(clock.Contains("Aujourd'hui") || clock.Contains("Hier"))
|
||||
{
|
||||
// Get hours & minutes
|
||||
IList<int> infosClock = clock.Split(':').Select(s => ParseUtil.CoerceInt(Regex.Match(s, @"\d+").Value)).ToList();
|
||||
|
||||
// Ago date with today
|
||||
date = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(infosClock[0]), Convert.ToInt32(infosClock[1]), DateTime.Now.Second);
|
||||
|
||||
// Set yesterday if necessary
|
||||
if (clock.Contains("Hier"))
|
||||
{
|
||||
// Remove one day from date
|
||||
date.AddDays(-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse Date if full
|
||||
date = DateTime.ParseExact(clock, "MM-dd-yyyy HH:mm", CultureInfo.GetCultureInfo("fr-FR"), DateTimeStyles.AssumeLocal);
|
||||
}
|
||||
|
||||
return date.ToUniversalTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download torrent file from tracker
|
||||
/// </summary>
|
||||
/// <param name="link">URL string</param>
|
||||
/// <returns></returns>
|
||||
public async override Task<byte[]> Download(Uri link)
|
||||
{
|
||||
var dl = link.AbsoluteUri;
|
||||
// This tracker need to thanks Uploader before getting torrent file...
|
||||
output("\nThis tracker needs you to thank uploader before downloading torrent!");
|
||||
|
||||
// Retrieving ID from link provided
|
||||
int id = ParseUtil.CoerceInt(Regex.Match(link.AbsoluteUri, @"\d+").Value);
|
||||
output("Torrent Requested ID: " + id);
|
||||
|
||||
// Building login form data
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "torrentid", id.ToString() },
|
||||
{ "_", string.Empty } // ~~ Strange, blank param...
|
||||
};
|
||||
|
||||
// Add emulated XHR request
|
||||
emulatedBrowserHeaders.Add("X-Prototype-Version", "1.6.0.3");
|
||||
emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
// Build WebRequest for thanks
|
||||
var myRequestThanks = new WebRequest()
|
||||
{
|
||||
Type = RequestType.POST,
|
||||
PostData = pairs,
|
||||
Url = TorrentThanksUrl,
|
||||
Headers = emulatedBrowserHeaders,
|
||||
Cookies = ConfigData.CookieHeader.Value,
|
||||
Referer = TorrentDescriptionUrl.Replace("{id}", id.ToString())
|
||||
};
|
||||
|
||||
// Get thanks page -- (not used, just for doing a request)
|
||||
latencyNow();
|
||||
output("Thanks user, to get download link for our torrent.. with " + TorrentThanksUrl);
|
||||
var thanksPage = await webclient.GetString(myRequestThanks);
|
||||
|
||||
// Get torrent file now
|
||||
output("Getting torrent file now....");
|
||||
var response = await base.Download(link);
|
||||
|
||||
// Remove our XHR request header
|
||||
emulatedBrowserHeaders.Remove("X-Prototype-Version");
|
||||
emulatedBrowserHeaders.Remove("X-Requested-With");
|
||||
|
||||
// Return content
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output message for logging or developpment (console)
|
||||
/// </summary>
|
||||
/// <param name="message">Message to output</param>
|
||||
/// <param name="level">Level for Logger</param>
|
||||
private void output(string message, string level = "debug")
|
||||
{
|
||||
// Check if we are in dev mode
|
||||
if (DevMode)
|
||||
{
|
||||
// Output message to console
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send message to logger with level
|
||||
switch (level)
|
||||
{
|
||||
default:
|
||||
goto case "debug";
|
||||
case "debug":
|
||||
// Only if Debug Level Enabled on Jackett
|
||||
if (Engine.Logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Debug(message);
|
||||
}
|
||||
break;
|
||||
case "info":
|
||||
logger.Info(message);
|
||||
break;
|
||||
case "error":
|
||||
logger.Error(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate Config entered by user on Jackett
|
||||
/// </summary>
|
||||
private void validateConfig()
|
||||
{
|
||||
output("\nValidating Settings ... \n");
|
||||
|
||||
// Check Username Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Username.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Password Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Password.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Max Page Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency Setting
|
||||
if (ConfigData.Latency.Value)
|
||||
{
|
||||
output("\nValidated Setting -- Latency Simulation enabled");
|
||||
|
||||
// Check Latency Start Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency End Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Browser Setting
|
||||
if (ConfigData.Browser.Value == true)
|
||||
{
|
||||
output("\nValidated Setting -- Browser Simulation enabled");
|
||||
|
||||
// Check ACCEPT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
|
||||
}
|
||||
|
||||
// Check ACCEPT-LANG header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
|
||||
}
|
||||
|
||||
// Check USER-AGENT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Browser simulation must be enabled (otherwhise, this provider will not work due to tracker's security)
|
||||
throw new ExceptionWithConfigData("Browser Simulation must be enabled for this provider to work, please enable it !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Dev Cache Settings
|
||||
if (ConfigData.HardDriveCache.Value == true)
|
||||
{
|
||||
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
|
||||
|
||||
// Check if Dev Mode enabled !
|
||||
if (!ConfigData.DevMode.Value)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Cache Keep Time Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete cache if previously existed
|
||||
cleanCacheStorage(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
361
src/Jackett/Indexers/RevolutionTT.cs
Normal file
361
src/Jackett/Indexers/RevolutionTT.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using CsQuery;
|
||||
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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class RevolutionTT : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LandingPageURL { get { return SiteLink + "login.php"; } }
|
||||
private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
private string GetRSSKeyUrl { get { return SiteLink + "getrss.php"; } }
|
||||
private string RSSUrl { get { return SiteLink + "rss.php?feed=dl&passkey="; } }
|
||||
private string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
private string DetailsURL { get { return SiteLink + "details.php?id={0}&hit=1"; } }
|
||||
|
||||
|
||||
new ConfigurationDataBasicLoginWithRSS configData
|
||||
{
|
||||
get { return (ConfigurationDataBasicLoginWithRSS)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public RevolutionTT(IIndexerManagerService i, Logger l, IWebClient wc, IProtectionService ps)
|
||||
: base(name: "RevolutionTT",
|
||||
description: "The Revolution has begun",
|
||||
link: "https://revolutiontt.me/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
downloadBase: "https://revolutiontt.me/download.php/",
|
||||
configData: new ConfigurationDataBasicLoginWithRSS())
|
||||
{
|
||||
|
||||
/* Original RevolutionTT Categories -
|
||||
|
||||
Anime - 23
|
||||
Appz/Misc - 22
|
||||
Appz/PC-ISO - 1
|
||||
E-Book - 36
|
||||
Games/PC-ISO - 4
|
||||
Games/PC-Rips - 21
|
||||
Games/PS3 - 16
|
||||
Games/Wii - 40
|
||||
Games/XBOX360 - 39
|
||||
Handheld/NDS - 35
|
||||
Handheld/PSP - 34
|
||||
Mac - 2
|
||||
Movies/BluRay - 10
|
||||
Movies/DVDR - 20
|
||||
Movies/HDx264 - 12
|
||||
Movies/Packs - 44
|
||||
Movies/SDx264 - 11
|
||||
Movies/XviD - 19
|
||||
Music - 6
|
||||
Music/FLAC - 8
|
||||
Music/Packs - 46
|
||||
MusicVideos - 29
|
||||
TV/DVDR - 43
|
||||
TV/HDx264 - 42
|
||||
TV/Packs - 45
|
||||
TV/SDx264 - 41
|
||||
TV/XViD - 7
|
||||
|
||||
*/
|
||||
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.Console);
|
||||
AddCategoryMapping("35", TorznabCatType.ConsoleNDS);
|
||||
AddCategoryMapping("34", TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping("40", TorznabCatType.ConsoleWii);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXbox);
|
||||
AddCategoryMapping("39", TorznabCatType.ConsoleXbox360);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleWiiwareVC);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXBOX360DLC);
|
||||
AddCategoryMapping("16", TorznabCatType.ConsolePS3);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleOther);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.Console3DS);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsolePSVita);
|
||||
AddCategoryMapping("40", TorznabCatType.ConsoleWiiU);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsoleXboxOne);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.ConsolePS4);
|
||||
AddCategoryMapping("44", TorznabCatType.Movies);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.MoviesForeign);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.MoviesOther);
|
||||
//Movies/DVDR, Movies/SDx264, Movies/XviD
|
||||
AddMultiCategoryMapping(TorznabCatType.MoviesSD, 20, 11, 19);
|
||||
//Movies/BluRay, Movies/HDx264
|
||||
AddMultiCategoryMapping(TorznabCatType.MoviesHD, 10, 12);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.Movies3D);
|
||||
AddCategoryMapping("10", TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping("20", TorznabCatType.MoviesDVD);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.MoviesWEBDL);
|
||||
//Music, Music/Packs
|
||||
AddMultiCategoryMapping(TorznabCatType.Audio, 6, 46);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.AudioMP3);
|
||||
AddCategoryMapping("29", TorznabCatType.AudioVideo);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.AudioAudiobook);
|
||||
AddCategoryMapping("8", TorznabCatType.AudioLossless);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.AudioOther);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.AudioForeign);
|
||||
AddCategoryMapping("21", TorznabCatType.PC);
|
||||
AddCategoryMapping("22", TorznabCatType.PC0day);
|
||||
AddCategoryMapping("4", TorznabCatType.PCISO);
|
||||
AddCategoryMapping("2", TorznabCatType.PCMac);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneOther);
|
||||
//Games/PC-ISO, Games/PC-Rips
|
||||
AddMultiCategoryMapping(TorznabCatType.PCGames, 4, 21);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneIOS);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.PCPhoneAndroid);
|
||||
AddCategoryMapping("45", TorznabCatType.TV);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.TVWEBDL);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.TVFOREIGN);
|
||||
//TV/DVDR, TV/SDx264, TV/XViD
|
||||
AddMultiCategoryMapping(TorznabCatType.TVSD, 43, 41, 7);
|
||||
AddCategoryMapping("42", TorznabCatType.TVHD);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.TVOTHER);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.TVSport);
|
||||
AddCategoryMapping("23", TorznabCatType.TVAnime);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.TVDocumentary);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXX);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXDVD);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXWMV);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXXviD);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXx264);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXOther);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXImageset);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.XXXPacks);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.Other);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.OtherMisc);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.OtherHashed);
|
||||
AddCategoryMapping("36", TorznabCatType.Books);
|
||||
AddCategoryMapping("36", TorznabCatType.BooksEbook);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.BooksComics);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.BooksMagazines);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.BooksTechnical);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.BooksOther);
|
||||
//AddCategoryMapping("cat_id", TorznabCatType.BooksForeign);
|
||||
|
||||
// RSS Textual categories
|
||||
AddCategoryMapping("Anime", TorznabCatType.TVAnime);
|
||||
AddCategoryMapping("Appz/Misc", TorznabCatType.PC0day);
|
||||
AddCategoryMapping("Appz/PC-ISO", TorznabCatType.Books);
|
||||
AddCategoryMapping("E-Book", TorznabCatType.BooksEbook);
|
||||
AddCategoryMapping("Games/PC-ISO", TorznabCatType.PCGames);
|
||||
AddCategoryMapping("Games/PC-Rips", TorznabCatType.PCGames);
|
||||
AddCategoryMapping("Games/PS3", TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping("Games/Wii", TorznabCatType.ConsoleWii);
|
||||
AddCategoryMapping("Games/XBOX360", TorznabCatType.ConsoleXbox360);
|
||||
AddCategoryMapping("Handheld/NDS", TorznabCatType.ConsoleNDS);
|
||||
AddCategoryMapping("Handheld/PSP", TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping("Mac", TorznabCatType.PCMac);
|
||||
AddCategoryMapping("Movies/BluRay", TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping("Movies/DVDR", TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping("Movies/HDx264", TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping("Movies/Packs", TorznabCatType.Movies);
|
||||
AddCategoryMapping("Movies/SDx264", TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping("Movies/XviD", TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping("Music", TorznabCatType.Audio);
|
||||
AddCategoryMapping("Music/FLAC", TorznabCatType.AudioLossless);
|
||||
AddCategoryMapping("Music/Packs", TorznabCatType.AudioOther);
|
||||
AddCategoryMapping("MusicVideos", TorznabCatType.AudioVideo);
|
||||
AddCategoryMapping("TV/DVDR", TorznabCatType.TV);
|
||||
AddCategoryMapping("TV/HDx264", TorznabCatType.TVHD);
|
||||
AddCategoryMapping("TV/Packs", TorznabCatType.TV);
|
||||
AddCategoryMapping("TV/SDx264", TorznabCatType.TVSD);
|
||||
AddCategoryMapping("TV/XViD", TorznabCatType.TVSD);
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value }
|
||||
};
|
||||
|
||||
// need to do an initial request to get PHP session cookie (any better way to do this?)
|
||||
var homePageLoad = await RequestLoginAndFollowRedirect(LandingPageURL, new Dictionary<string, string> { }, null, true, null, SiteLink);
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, homePageLoad.Cookies, true, null, LandingPageURL);
|
||||
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("/logout.php"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var messageEl = dom[".error"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
|
||||
// Store RSS key from feed generator page
|
||||
try
|
||||
{
|
||||
var rssParams = new Dictionary<string, string> {
|
||||
{ "feed", "dl" }
|
||||
};
|
||||
var rssPage = await PostDataWithCookies(GetRSSKeyUrl, rssParams, result.Cookies);
|
||||
var match = Regex.Match(rssPage.Content, "(?<=passkey\\=)([a-zA-z0-9]*)");
|
||||
configData.RSSKey.Value = match.Success ? match.Value : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(configData.RSSKey.Value))
|
||||
throw new Exception("Failed to get RSS Key");
|
||||
SaveConfig();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
IsConfigured = false;
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
|
||||
// If query is empty, use the RSS Feed
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
var rssPage = await RequestStringWithCookiesAndRetry(RSSUrl + configData.RSSKey.Value);
|
||||
var rssDoc = XDocument.Parse(rssPage.Content);
|
||||
|
||||
foreach (var item in rssDoc.Descendants("item"))
|
||||
{
|
||||
var title = item.Descendants("title").First().Value;
|
||||
var description = item.Descendants("description").First().Value;
|
||||
var link = item.Descendants("link").First().Value;
|
||||
var date = item.Descendants("pubDate").First().Value;
|
||||
|
||||
var torrentIdMatch = Regex.Match(link, "(?<=download\\.php/)([a-zA-z0-9]*)");
|
||||
var torrentId = torrentIdMatch.Success ? torrentIdMatch.Value : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(torrentId))
|
||||
throw new Exception("Missing torrent id");
|
||||
|
||||
var infoMatch = Regex.Match(description, @"Category:\W(?<cat>.*)\W\n\WSize:\W(?<size>.*)\n\WStatus:\W(?<seeders>.*)\Wseeder(.*)\Wand\W(?<leechers>.*)\Wleecher(.*)\n\WAdded:\W(?<added>.*)\n\WDescription:");
|
||||
if (!infoMatch.Success)
|
||||
throw new Exception("Unable to find info");
|
||||
|
||||
var imdbMatch = Regex.Match(description, "(?<=http://www.imdb.com/title/tt)([0-9]*)");
|
||||
long? imdbID = null;
|
||||
if(imdbMatch.Success)
|
||||
{
|
||||
long l;
|
||||
if(long.TryParse(imdbMatch.Value, out l))
|
||||
{
|
||||
imdbID = l;
|
||||
}
|
||||
}
|
||||
|
||||
var release = new ReleaseInfo()
|
||||
{
|
||||
Title = title,
|
||||
Description = title,
|
||||
Guid = new Uri(string.Format(DetailsURL, torrentId)),
|
||||
Comments = new Uri(string.Format(DetailsURL, torrentId) + "&tocomm=1"),
|
||||
PublishDate = DateTime.ParseExact(infoMatch.Groups["added"].Value, "yyyy-MM-dd H:mm:ss", CultureInfo.InvariantCulture), //2015-08-08 21:20:31
|
||||
Link = new Uri(link),
|
||||
Seeders = ParseUtil.CoerceInt(infoMatch.Groups["seeders"].Value == "no" ? "0" : infoMatch.Groups["seeders"].Value),
|
||||
Peers = ParseUtil.CoerceInt(infoMatch.Groups["leechers"].Value == "no" ? "0" : infoMatch.Groups["leechers"].Value),
|
||||
Size = ReleaseInfo.GetBytes(infoMatch.Groups["size"].Value),
|
||||
Category = MapTrackerCatToNewznab(infoMatch.Groups["cat"].Value),
|
||||
Imdb = imdbID
|
||||
};
|
||||
|
||||
// if unknown category, set to "other"
|
||||
if (release.Category == 0)
|
||||
release.Category = 7000;
|
||||
|
||||
release.Peers += release.Seeders;
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
searchUrl += "?titleonly=1&search=" + HttpUtility.UrlEncode(searchString);
|
||||
string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
|
||||
|
||||
var cats = MapTorznabCapsToTrackers(query);
|
||||
if (cats.Count > 0)
|
||||
{
|
||||
foreach (var cat in cats)
|
||||
{
|
||||
searchUrl += "&c" + cat + "=1";
|
||||
}
|
||||
}
|
||||
|
||||
var results = await RequestStringWithCookiesAndRetry(searchUrl);
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
|
||||
// table header is the first <tr> in table body, get all rows except this
|
||||
CQ qRows = dom["#torrents-table > tbody > tr:not(:first-child)"];
|
||||
|
||||
foreach (var row in qRows)
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
var qRow = row.Cq();
|
||||
|
||||
var debug = qRow.Html();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 172800;
|
||||
|
||||
CQ qLink = qRow.Find(".br_right > a").First();
|
||||
release.Guid = new Uri(SiteLink + qLink.Attr("href"));
|
||||
release.Comments = new Uri(SiteLink + qLink.Attr("href") + "&tocomm=1");
|
||||
release.Title = qLink.Find("b").Text();
|
||||
release.Description = release.Title;
|
||||
|
||||
release.Link = new Uri(SiteLink + qRow.Find("td:nth-child(4) > a").Attr("href"));
|
||||
|
||||
var dateString = qRow.Find("td:nth-child(6) nobr")[0].InnerText.Trim();
|
||||
//"2015-04-25 23:38:12"
|
||||
//"yyyy-MMM-dd hh:mm:ss"
|
||||
release.PublishDate = DateTime.ParseExact(dateString, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture);
|
||||
|
||||
var sizeStr = qRow.Children().ElementAt(6).InnerText.Trim();
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:nth-child(9)").Text());
|
||||
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find("td:nth-child(10)").Text());
|
||||
|
||||
var category = qRow.Find(".br_type > a").Attr("href").Replace("browse.php?cat=", string.Empty);
|
||||
release.Category = MapTrackerCatToNewznab(category);
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,164 +1,165 @@
|
||||
using CsQuery;
|
||||
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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
class SceneAccess : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login"; } }
|
||||
private string SearchUrl { get { return SiteLink + "all?search={0}&method=2"; } }
|
||||
|
||||
new ConfigurationDataBasicLogin configData
|
||||
{
|
||||
get { return (ConfigurationDataBasicLogin)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "SceneAccess",
|
||||
description: "Your gateway to the scene",
|
||||
link: "https://sceneaccess.eu/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
{
|
||||
|
||||
AddCategoryMapping(8, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(22, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(4, TorznabCatType.Movies);
|
||||
|
||||
AddCategoryMapping(27, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(17, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(11, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(26, TorznabCatType.TV);
|
||||
|
||||
AddCategoryMapping(3, TorznabCatType.PCGames);
|
||||
AddCategoryMapping(5, TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping(20, TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping(28, TorznabCatType.TV);
|
||||
AddCategoryMapping(23, TorznabCatType.Console);
|
||||
AddCategoryMapping(29, TorznabCatType.Console);
|
||||
|
||||
AddCategoryMapping(40, TorznabCatType.AudioLossless);
|
||||
AddCategoryMapping(13, TorznabCatType.AudioMP3);
|
||||
AddCategoryMapping(15, TorznabCatType.AudioVideo);
|
||||
|
||||
AddCategoryMapping(1, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(2, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(14, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(21, TorznabCatType.Other);
|
||||
|
||||
AddCategoryMapping(41, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(42, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(43, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(44, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(45, TorznabCatType.TVSD);
|
||||
|
||||
AddCategoryMapping(12, TorznabCatType.XXXXviD);
|
||||
AddCategoryMapping(35, TorznabCatType.XXXx264);
|
||||
AddCategoryMapping(36, TorznabCatType.XXX);
|
||||
|
||||
AddCategoryMapping(30, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(31, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(32, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(33, TorznabCatType.TVFOREIGN);
|
||||
AddCategoryMapping(34, TorznabCatType.TVFOREIGN);
|
||||
|
||||
AddCategoryMapping(4, TorznabCatType.Movies);
|
||||
AddCategoryMapping(37, TorznabCatType.XXX);
|
||||
AddCategoryMapping(38, TorznabCatType.Audio);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "submit", "come on in" }
|
||||
};
|
||||
|
||||
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, LoginUrl);
|
||||
await ConfigureIfOK(result.Cookies + " " + loginPage.Cookies, result.Content != null && result.Content.Contains("nav_profile"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var messageEl = dom["#login_box_desc"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var results = await RequestStringWithCookiesAndRetry(string.Format(SearchUrl, query.GetQueryString()));
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
var rows = dom["#torrents-table > tbody > tr.tt_row"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 129600;
|
||||
release.Title = qRow.Find(".ttr_name > a").Text();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(SiteLink + qRow.Find(".ttr_name > a").Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(SiteLink + qRow.Find(".td_dl > a").Attr("href"));
|
||||
|
||||
var sizeStr = qRow.Find(".ttr_size").Contents()[0].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
var timeStr = qRow.Find(".ttr_added").Text();
|
||||
DateTime time;
|
||||
if (DateTime.TryParseExact(timeStr, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
|
||||
{
|
||||
release.PublishDate = time;
|
||||
}
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".ttr_seeders").Text());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find(".ttr_leechers").Text()) + release.Seeders;
|
||||
|
||||
var cat = qRow.Find(".ttr_type a").Attr("href").Replace("?cat=",string.Empty);
|
||||
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
||||
using CsQuery;
|
||||
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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Web;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
class SceneAccess : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login"; } }
|
||||
private string SearchUrl { get { return SiteLink + "all?search={0}&method=2"; } }
|
||||
|
||||
new ConfigurationDataBasicLogin configData
|
||||
{
|
||||
get { return (ConfigurationDataBasicLogin)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l, IProtectionService ps)
|
||||
: base(name: "SceneAccess",
|
||||
description: "Your gateway to the scene",
|
||||
link: "https://sceneaccess.eu/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: c,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLogin())
|
||||
{
|
||||
|
||||
AddCategoryMapping(8, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(22, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(7, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(4, TorznabCatType.Movies);
|
||||
|
||||
AddCategoryMapping(27, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(17, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(11, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(26, TorznabCatType.TV);
|
||||
|
||||
AddCategoryMapping(3, TorznabCatType.PCGames);
|
||||
AddCategoryMapping(5, TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping(20, TorznabCatType.ConsolePSP);
|
||||
AddCategoryMapping(28, TorznabCatType.TV);
|
||||
AddCategoryMapping(23, TorznabCatType.Console);
|
||||
AddCategoryMapping(29, TorznabCatType.Console);
|
||||
|
||||
AddCategoryMapping(40, TorznabCatType.AudioLossless);
|
||||
AddCategoryMapping(13, TorznabCatType.AudioMP3);
|
||||
AddCategoryMapping(15, TorznabCatType.AudioVideo);
|
||||
|
||||
AddCategoryMapping(1, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(2, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(14, TorznabCatType.PCISO);
|
||||
AddCategoryMapping(21, TorznabCatType.Other);
|
||||
|
||||
AddCategoryMapping(41, TorznabCatType.MoviesHD);
|
||||
AddCategoryMapping(42, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(43, TorznabCatType.MoviesSD);
|
||||
AddCategoryMapping(44, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(45, TorznabCatType.TVSD);
|
||||
|
||||
AddCategoryMapping(12, TorznabCatType.XXXXviD);
|
||||
AddCategoryMapping(35, TorznabCatType.XXXx264);
|
||||
AddCategoryMapping(36, TorznabCatType.XXX);
|
||||
|
||||
AddCategoryMapping(30, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(31, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(32, TorznabCatType.MoviesForeign);
|
||||
AddCategoryMapping(33, TorznabCatType.TVFOREIGN);
|
||||
AddCategoryMapping(34, TorznabCatType.TVFOREIGN);
|
||||
|
||||
AddCategoryMapping(4, TorznabCatType.Movies);
|
||||
AddCategoryMapping(37, TorznabCatType.XXX);
|
||||
AddCategoryMapping(38, TorznabCatType.Audio);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value },
|
||||
{ "submit", "come on in" }
|
||||
};
|
||||
|
||||
var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, LoginUrl);
|
||||
await ConfigureIfOK(result.Cookies + " " + loginPage.Cookies, result.Content != null && result.Content.Contains("nav_profile"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var messageEl = dom["#login_box_desc"];
|
||||
var errorMessage = messageEl.Text().Trim();
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var results = await RequestStringWithCookiesAndRetry(string.Format(SearchUrl, HttpUtility.UrlEncode(query.GetQueryString())));
|
||||
|
||||
try
|
||||
{
|
||||
CQ dom = results.Content;
|
||||
var rows = dom["#torrents-table > tbody > tr.tt_row"];
|
||||
foreach (var row in rows)
|
||||
{
|
||||
CQ qRow = row.Cq();
|
||||
var release = new ReleaseInfo();
|
||||
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 129600;
|
||||
release.Title = qRow.Find(".ttr_name > a").Text();
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(SiteLink + qRow.Find(".ttr_name > a").Attr("href"));
|
||||
release.Comments = release.Guid;
|
||||
release.Link = new Uri(SiteLink + qRow.Find(".td_dl > a").Attr("href"));
|
||||
|
||||
var sizeStr = qRow.Find(".ttr_size").Contents()[0].NodeValue;
|
||||
release.Size = ReleaseInfo.GetBytes(sizeStr);
|
||||
|
||||
var timeStr = qRow.Find(".ttr_added").Text();
|
||||
DateTime time;
|
||||
if (DateTime.TryParseExact(timeStr, "yyyy-MM-ddHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
|
||||
{
|
||||
release.PublishDate = time;
|
||||
}
|
||||
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find(".ttr_seeders").Text());
|
||||
release.Peers = ParseUtil.CoerceInt(qRow.Find(".ttr_leechers").Text()) + release.Seeders;
|
||||
|
||||
var cat = qRow.Find(".ttr_type a").Attr("href").Replace("?cat=",string.Empty);
|
||||
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(results.Content, ex);
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ namespace Jackett.Indexers
|
||||
public TVChaosUK(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "TV Chaos",
|
||||
description: "Total Chaos",
|
||||
link: "https://tvchaosuk.com/",
|
||||
link: "https://www.tvchaosuk.com/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: wc,
|
||||
|
945
src/Jackett/Indexers/WiHD.cs
Normal file
945
src/Jackett/Indexers/WiHD.cs
Normal file
@@ -0,0 +1,945 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CsQuery;
|
||||
using Jackett.Models;
|
||||
using Jackett.Models.IndexerConfig.Bespoke;
|
||||
using Jackett.Services;
|
||||
using Jackett.Utils;
|
||||
using Jackett.Utils.Clients;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for WiHD Private French Tracker
|
||||
/// </summary>
|
||||
public class WiHD : BaseIndexer, IIndexer
|
||||
{
|
||||
private string LoginUrl { get { return SiteLink + "login"; } }
|
||||
private string LoginCheckUrl { get { return SiteLink + "login_check"; } }
|
||||
private string SearchUrl { get { return SiteLink + "torrent/ajaxfiltertorrent/"; } }
|
||||
private bool Latency { get { return ConfigData.Latency.Value; } }
|
||||
private bool DevMode { get { return ConfigData.DevMode.Value; } }
|
||||
private bool CacheMode { get { return ConfigData.HardDriveCache.Value; } }
|
||||
private string directory { get { return System.IO.Path.GetTempPath() + "Jackett\\" + MethodBase.GetCurrentMethod().DeclaringType.Name + "\\"; } }
|
||||
|
||||
private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
|
||||
private CQ fDom = null;
|
||||
|
||||
private ConfigurationDataWiHD ConfigData
|
||||
{
|
||||
get { return (ConfigurationDataWiHD)configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public WiHD(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
|
||||
: base(
|
||||
name: "WiHD",
|
||||
description: "Your World in High Definition",
|
||||
link: "http://world-in-hd.net/",
|
||||
caps: new TorznabCapabilities(),
|
||||
manager: i,
|
||||
client: w,
|
||||
logger: l,
|
||||
p: ps,
|
||||
downloadBase: "http://world-in-hd.net/torrents/download/",
|
||||
configData: new ConfigurationDataWiHD())
|
||||
{
|
||||
// Clean capabilities
|
||||
TorznabCaps.Categories.Clear();
|
||||
|
||||
// Movies
|
||||
AddCategoryMapping("565af82b1fd35761568b4572", TorznabCatType.MoviesHD); // 1080P
|
||||
AddCategoryMapping("565af82b1fd35761568b4574", TorznabCatType.MoviesHD); // 720P
|
||||
AddCategoryMapping("565af82b1fd35761568b4576", TorznabCatType.MoviesHD); // HDTV
|
||||
AddCategoryMapping("565af82b1fd35761568b4578", TorznabCatType.MoviesBluRay); // Bluray
|
||||
AddCategoryMapping("565af82b1fd35761568b457a", TorznabCatType.MoviesBluRay); // Bluray Remux
|
||||
AddCategoryMapping("565af82b1fd35761568b457c", TorznabCatType.Movies3D); // Bluray 3D
|
||||
|
||||
// TV
|
||||
AddCategoryMapping("565af82d1fd35761568b4587", TorznabCatType.TVHD); // 1080P
|
||||
AddCategoryMapping("565af82d1fd35761568b4589", TorznabCatType.TVHD); // 720P
|
||||
AddCategoryMapping("565af82d1fd35761568b458b", TorznabCatType.TVHD); // HDTV
|
||||
AddCategoryMapping("565af82d1fd35761568b458d", TorznabCatType.TVHD); // Bluray
|
||||
AddCategoryMapping("565af82d1fd35761568b458f", TorznabCatType.TVHD); // Bluray Remux
|
||||
AddCategoryMapping("565af82d1fd35761568b4591", TorznabCatType.TVHD); // Bluray 3D
|
||||
|
||||
// Anime
|
||||
AddCategoryMapping("565af82d1fd35761568b459c", TorznabCatType.TVAnime); // 1080P
|
||||
AddCategoryMapping("565af82d1fd35761568b459e", TorznabCatType.TVAnime); // 720P
|
||||
AddCategoryMapping("565af82d1fd35761568b45a0", TorznabCatType.TVAnime); // HDTV
|
||||
AddCategoryMapping("565af82d1fd35761568b45a2", TorznabCatType.TVAnime); // Bluray
|
||||
AddCategoryMapping("565af82d1fd35761568b45a4", TorznabCatType.TVAnime); // Bluray Remux
|
||||
AddCategoryMapping("565af82d1fd35761568b45a6", TorznabCatType.TVAnime); // Bluray 3D
|
||||
|
||||
// Other
|
||||
AddCategoryMapping("565af82d1fd35761568b45af", TorznabCatType.PC); // Apps
|
||||
AddCategoryMapping("565af82d1fd35761568b45b1", TorznabCatType.AudioVideo); // Clips
|
||||
AddCategoryMapping("565af82d1fd35761568b45b3", TorznabCatType.AudioOther); // Audios Tracks of Movies/TV/Anime
|
||||
AddCategoryMapping("565af82d1fd35761568b45b5", TorznabCatType.TVDocumentary); // Documentary
|
||||
AddCategoryMapping("565af82d1fd35761568b45b7", TorznabCatType.MoviesBluRay); // Bluray (ALL)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure our WiHD Provider
|
||||
/// </summary>
|
||||
/// <param name="configJson">Our params in Json</param>
|
||||
/// <returns>Configuration state</returns>
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
// Retrieve config values set by Jackett's user
|
||||
ConfigData.LoadValuesFromJson(configJson);
|
||||
|
||||
// Check & Validate Config
|
||||
validateConfig();
|
||||
|
||||
// Setting our data for a better emulated browser (maximum security)
|
||||
// TODO: Encoded Content not supported by Jackett at this time
|
||||
// emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
|
||||
|
||||
// If we want to simulate a browser
|
||||
if (ConfigData.Browser.Value) {
|
||||
|
||||
// Clean headers
|
||||
emulatedBrowserHeaders.Clear();
|
||||
|
||||
// Inject headers
|
||||
emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
|
||||
emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
|
||||
emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
|
||||
emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
|
||||
}
|
||||
|
||||
|
||||
// Getting login form to retrieve CSRF token
|
||||
var myRequest = new Utils.Clients.WebRequest()
|
||||
{
|
||||
Url = LoginUrl
|
||||
};
|
||||
|
||||
// Add our headers to request
|
||||
myRequest.Headers = emulatedBrowserHeaders;
|
||||
|
||||
// Get login page
|
||||
var loginPage = await webclient.GetString(myRequest);
|
||||
|
||||
// Retrieving our CSRF token
|
||||
CQ loginPageDom = loginPage.Content;
|
||||
var csrfToken = loginPageDom["input[name=\"_csrf_token\"]"].Last();
|
||||
|
||||
// Building login form data
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "_csrf_token", csrfToken.Attr("value") },
|
||||
{ "_username", ConfigData.Username.Value },
|
||||
{ "_password", ConfigData.Password.Value },
|
||||
{ "_remember_me", "on" },
|
||||
{ "_submit", "" }
|
||||
};
|
||||
|
||||
// Do the login
|
||||
var request = new Utils.Clients.WebRequest(){
|
||||
Cookies = loginPage.Cookies,
|
||||
PostData = pairs,
|
||||
Referer = LoginUrl,
|
||||
Type = RequestType.POST,
|
||||
Url = LoginUrl,
|
||||
Headers = emulatedBrowserHeaders
|
||||
};
|
||||
|
||||
// Perform loggin
|
||||
latencyNow();
|
||||
output("\nPerform loggin.. with " + LoginCheckUrl);
|
||||
var response = await RequestLoginAndFollowRedirect(LoginCheckUrl, pairs, loginPage.Cookies, true, null, null);
|
||||
|
||||
// Test if we are logged in
|
||||
await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout"), () =>
|
||||
{
|
||||
// Oops, unable to login
|
||||
output("-> Login failed", "error");
|
||||
throw new ExceptionWithConfigData("Failed to login", configData);
|
||||
});
|
||||
|
||||
output("-> Login Success");
|
||||
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute our search query
|
||||
/// </summary>
|
||||
/// <param name="query">Query</param>
|
||||
/// <returns>Releases</returns>
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var torrentRowList = new List<CQ>();
|
||||
var searchTerm = query.GetQueryString();
|
||||
var searchUrl = SearchUrl;
|
||||
int nbResults = 0;
|
||||
int pageLinkCount = 0;
|
||||
|
||||
// Check cache first so we don't query the server (if search term used or not in dev mode)
|
||||
if (!DevMode && !string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
lock (cache)
|
||||
{
|
||||
// Remove old cache items
|
||||
CleanCache();
|
||||
|
||||
// Search in cache
|
||||
var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
|
||||
if (cachedResult != null)
|
||||
return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// Add emulated XHR request
|
||||
emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
// Build our query
|
||||
var request = buildQuery(searchTerm, query, searchUrl);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult results = await queryExec(request);
|
||||
fDom = results.Content;
|
||||
|
||||
try
|
||||
{
|
||||
// Find number of results
|
||||
nbResults = ParseUtil.CoerceInt(Regex.Match(fDom["div.ajaxtotaltorrentcount"].Text(), @"\d+").Value);
|
||||
|
||||
// Find torrent rows
|
||||
var firstPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
|
||||
|
||||
// Check if there are pagination links at bottom
|
||||
Boolean pagination = (nbResults != 0);
|
||||
|
||||
// If pagination available
|
||||
if (pagination)
|
||||
{
|
||||
// Calculate numbers of pages available for this search query (Based on number results and number of torrents on first page)
|
||||
pageLinkCount = (int)Math.Ceiling((double)nbResults / firstPageRows.Length);
|
||||
}
|
||||
else {
|
||||
// Check if we have a minimum of one result
|
||||
if (firstPageRows.Length >= 1)
|
||||
{
|
||||
// Set page count arbitrary to one
|
||||
pageLinkCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
output("\nNo result found for your query, please try another search term ...\n", "info");
|
||||
// No result found for this query
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
output("\nFound " + nbResults + " result(s) in " + pageLinkCount + " page(s) for this query !");
|
||||
output("\nThere are " + firstPageRows.Length + " results on the first page !");
|
||||
|
||||
// If we have a term used for search and pagination result superior to one
|
||||
if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
|
||||
{
|
||||
// Starting with page #2
|
||||
for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
|
||||
{
|
||||
output("\nProcessing page #" + i);
|
||||
|
||||
// Request our page
|
||||
latencyNow();
|
||||
|
||||
// Build our query
|
||||
var pageRequest = buildQuery(searchTerm, query, searchUrl, i);
|
||||
|
||||
// Getting results & Store content
|
||||
WebClientStringResult pageResults = await queryExec(pageRequest);
|
||||
|
||||
// Assign response
|
||||
fDom = pageResults.Content;
|
||||
|
||||
// Process page results
|
||||
var additionalPageRows = findTorrentRows();
|
||||
|
||||
// Add them to torrents list
|
||||
torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
|
||||
}
|
||||
}
|
||||
|
||||
// Loop on results
|
||||
foreach (CQ tRow in torrentRowList)
|
||||
{
|
||||
output("\n=>> Torrent #" + (releases.Count + 1));
|
||||
|
||||
// Release Name
|
||||
string name = tRow.Find(".torrent-h3 > h3 > a").Attr("title").ToString();
|
||||
output("Release: " + name);
|
||||
|
||||
// Category
|
||||
string categoryID = tRow.Find(".category > img").Attr("src").Split('/').Last().ToString();
|
||||
string categoryName = tRow.Find(".category > img").Attr("title").ToString();
|
||||
output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)) + " (" + categoryName + ")");
|
||||
|
||||
// Uploader
|
||||
string uploader = tRow.Find(".uploader > span > a").Attr("title").ToString();
|
||||
output("Uploader: " + uploader);
|
||||
|
||||
// Seeders
|
||||
int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".seeders")[0].LastChild.ToString(), @"\d+").Value);
|
||||
output("Seeders: " + seeders);
|
||||
|
||||
// Leechers
|
||||
int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".leechers")[0].LastChild.ToString(), @"\d+").Value);
|
||||
output("Leechers: " + leechers);
|
||||
|
||||
// Completed
|
||||
int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".completed")[0].LastChild.ToString(), @"\d+").Value);
|
||||
output("Completed: " + completed);
|
||||
|
||||
// Comments
|
||||
int comments = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".comments")[0].LastChild.ToString(), @"\d+").Value);
|
||||
output("Comments: " + comments);
|
||||
|
||||
// Size & Publish Date
|
||||
string infosData = tRow.Find(".torrent-h3 > span")[0].LastChild.ToString().Trim();
|
||||
IList<string> infosList = infosData.Split('-').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
|
||||
|
||||
// --> Size
|
||||
var size = ReleaseInfo.GetBytes(infosList[1].Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb"));
|
||||
output("Size: " + infosList[1] + " (" + size + " bytes)");
|
||||
|
||||
// --> Publish Date
|
||||
IList<string> clockList = infosList[0].Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
|
||||
var clock = agoToDate(clockList);
|
||||
output("Released on: " + clock.ToString());
|
||||
|
||||
// Torrent Details URL
|
||||
string details = tRow.Find(".torrent-h3 > h3 > a").Attr("href").ToString().TrimStart('/');
|
||||
Uri detailsLink = new Uri(SiteLink + details);
|
||||
output("Details: " + detailsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Comments URL
|
||||
Uri commentsLink = new Uri(SiteLink + details + "#tab_2");
|
||||
output("Comments Link: " + commentsLink.AbsoluteUri);
|
||||
|
||||
// Torrent Download URL
|
||||
string download = tRow.Find(".download-item > a").Attr("href").ToString().TrimStart('/');
|
||||
Uri downloadLink = new Uri(SiteLink + download);
|
||||
output("Download Link: " + downloadLink.AbsoluteUri);
|
||||
|
||||
// Building release infos
|
||||
var release = new ReleaseInfo();
|
||||
release.Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName));
|
||||
release.Title = name;
|
||||
release.Seeders = seeders;
|
||||
release.Peers = seeders + leechers;
|
||||
release.MinimumRatio = 1;
|
||||
release.MinimumSeedTime = 345600;
|
||||
release.PublishDate = clock;
|
||||
release.Size = size;
|
||||
release.Guid = detailsLink;
|
||||
release.Comments = commentsLink;
|
||||
release.Link = downloadLink;
|
||||
releases.Add(release);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError("Error, unable to parse result \n" + ex.StackTrace, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Remove our XHR request header
|
||||
emulatedBrowserHeaders.Remove("X-Requested-With");
|
||||
}
|
||||
|
||||
// Return found releases
|
||||
return releases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build query to process
|
||||
/// </summary>
|
||||
/// <param name="term">Term to search</param>
|
||||
/// <param name="query">Torznab Query for categories mapping</param>
|
||||
/// <param name="url">Search url for provider</param>
|
||||
/// <param name="page">Page number to request</param>
|
||||
/// <returns>URL to query for parsing and processing results</returns>
|
||||
private string buildQuery(string term, TorznabQuery query, string url, int page = 1)
|
||||
{
|
||||
var parameters = new NameValueCollection();
|
||||
List<string> categoriesList = MapTorznabCapsToTrackers(query);
|
||||
string categories = null;
|
||||
|
||||
// If search term not provided
|
||||
if (string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
// Showing all torrents (just for output function)
|
||||
term = "null";
|
||||
}
|
||||
|
||||
// Encode & Add search term to URL
|
||||
url += Uri.EscapeDataString(term);
|
||||
|
||||
// Check if we are processing a new page
|
||||
if (page > 1)
|
||||
{
|
||||
// Adding page number to query
|
||||
url += "/" + page.ToString();
|
||||
}
|
||||
|
||||
// Adding interrogation point
|
||||
url += "?";
|
||||
|
||||
// Building our tracker query
|
||||
parameters.Add("exclu", Convert.ToInt32(ConfigData.Exclusive.Value).ToString());
|
||||
parameters.Add("freeleech", Convert.ToInt32(ConfigData.Freeleech.Value).ToString());
|
||||
parameters.Add("reseed", Convert.ToInt32(ConfigData.Reseed.Value).ToString());
|
||||
|
||||
// Loop on Categories needed
|
||||
foreach (string category in categoriesList)
|
||||
{
|
||||
// If last, build !
|
||||
if(categoriesList.Last() == category)
|
||||
{
|
||||
// Adding previous categories to URL with latest category
|
||||
parameters.Add(Uri.EscapeDataString("subcat[]"), category + categories);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build categories parameter
|
||||
categories += "&" + Uri.EscapeDataString("subcat[]") + "=" + category;
|
||||
}
|
||||
}
|
||||
|
||||
// Add timestamp as a query param (for no caching)
|
||||
parameters.Add("_", UnixTimeNow().ToString());
|
||||
|
||||
// Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong subcat[] param)
|
||||
url += string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
|
||||
|
||||
output("\nBuilded query for \"" + term + "\"... " + url);
|
||||
|
||||
// Return our search url
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switch Method for Querying
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryExec(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Switch in we are in DEV mode with Hard Drive Cache or not
|
||||
if (DevMode && CacheMode)
|
||||
{
|
||||
// Check Cache before querying and load previous results if available
|
||||
results = await queryCache(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Cache by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryCache(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Create Directory if not exist
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
|
||||
// Clean Storage Provider Directory from outdated cached queries
|
||||
cleanCacheStorage();
|
||||
|
||||
// Remove timestamp from query
|
||||
string file = string.Join("&", Regex.Split(request, "&").Where(s => !Regex.IsMatch(s, @"_=\d")).ToList());
|
||||
|
||||
// Create fingerprint for request
|
||||
file = directory + file.GetHashCode() + ".json";
|
||||
|
||||
// Checking modes states
|
||||
if (System.IO.File.Exists(file))
|
||||
{
|
||||
// File exist... loading it right now !
|
||||
output("Loading results from hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
results = JsonConvert.DeserializeObject<WebClientStringResult>(System.IO.File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No cached file found, querying tracker directly
|
||||
results = await queryTracker(request);
|
||||
|
||||
// Cached file didn't exist for our query, writing it right now !
|
||||
output("Writing results to hard drive cache ..." + request.GetHashCode() + ".json");
|
||||
System.IO.File.WriteAllText(file, JsonConvert.SerializeObject(results));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Torrents Page from Tracker by Query Provided
|
||||
/// </summary>
|
||||
/// <param name="request">URL created by Query Builder</param>
|
||||
/// <returns>Results from query</returns>
|
||||
private async Task<WebClientStringResult> queryTracker(string request)
|
||||
{
|
||||
WebClientStringResult results = null;
|
||||
|
||||
// Cache mode not enabled or cached file didn't exist for our query
|
||||
output("\nQuerying tracker for results....");
|
||||
|
||||
// Request our first page
|
||||
latencyNow();
|
||||
results = await RequestStringWithCookiesAndRetry(request, null, null, emulatedBrowserHeaders);
|
||||
|
||||
// Return results from tracker
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean Hard Drive Cache Storage
|
||||
/// </summary>
|
||||
/// <param name="force">Force Provider Folder deletion</param>
|
||||
private void cleanCacheStorage(Boolean force = false)
|
||||
{
|
||||
// Check cleaning method
|
||||
if (force)
|
||||
{
|
||||
// Deleting Provider Storage folder and all files recursively
|
||||
output("\nDeleting Provider Storage folder and all files recursively ...");
|
||||
|
||||
// Check if directory exist
|
||||
if (System.IO.Directory.Exists(directory))
|
||||
{
|
||||
// Delete storage directory of provider
|
||||
System.IO.Directory.Delete(directory, true);
|
||||
output("-> Storage folder deleted successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No directory, so nothing to do
|
||||
output("-> No Storage folder found for this provider !");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
// Check if there is file older than ... and delete them
|
||||
output("\nCleaning Provider Storage folder... in progress.");
|
||||
System.IO.Directory.GetFiles(directory)
|
||||
.Select(f => new System.IO.FileInfo(f))
|
||||
.Where(f => f.LastAccessTime < DateTime.Now.AddMilliseconds(-Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value)))
|
||||
.ToList()
|
||||
.ForEach(f => {
|
||||
output("Deleting cached file << " + f.Name + " >> ... done.");
|
||||
f.Delete();
|
||||
i++;
|
||||
});
|
||||
|
||||
// Inform on what was cleaned during process
|
||||
if (i > 0)
|
||||
{
|
||||
output("-> Deleted " + i + " cached files during cleaning.");
|
||||
}
|
||||
else {
|
||||
output("-> Nothing deleted during cleaning.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a random fake latency to avoid detection on tracker side
|
||||
/// </summary>
|
||||
private void latencyNow()
|
||||
{
|
||||
// Need latency ?
|
||||
if(Latency)
|
||||
{
|
||||
// Generate a random value in our range
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
output("\nLatency Faker => Sleeping for " + waiting + " ms...");
|
||||
|
||||
// Sleep now...
|
||||
System.Threading.Thread.Sleep(waiting);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an UTC Unix TimeStamp
|
||||
/// </summary>
|
||||
/// <returns>Unix TimeStamp</returns>
|
||||
private long UnixTimeNow()
|
||||
{
|
||||
var timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
|
||||
return (long)timeSpan.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find torrent rows in search pages
|
||||
/// </summary>
|
||||
/// <returns>JQuery Object</returns>
|
||||
private CQ findTorrentRows()
|
||||
{
|
||||
// Return all occurencis of torrents found
|
||||
return fDom[".torrent-item"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert Ago date to DateTime
|
||||
/// </summary>
|
||||
/// <param name="clockList"></param>
|
||||
/// <returns>A DateTime</returns>
|
||||
private DateTime agoToDate(IList<string> clockList)
|
||||
{
|
||||
DateTime release = DateTime.Now;
|
||||
foreach(var ago in clockList)
|
||||
{
|
||||
// Check for years
|
||||
if(ago.Contains("Années") || ago.Contains("Année"))
|
||||
{
|
||||
// Number of years to remove
|
||||
int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddYears(-years);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for months
|
||||
else if (ago.Contains("Mois"))
|
||||
{
|
||||
// Number of months to remove
|
||||
int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddMonths(-months);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for days
|
||||
else if (ago.Contains("Jours") || ago.Contains("Jour"))
|
||||
{
|
||||
// Number of days to remove
|
||||
int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddDays(-days);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for hours
|
||||
else if (ago.Contains("Heures") || ago.Contains("Heure"))
|
||||
{
|
||||
// Number of hours to remove
|
||||
int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddHours(-hours);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for minutes
|
||||
else if (ago.Contains("Minutes") || ago.Contains("Minute"))
|
||||
{
|
||||
// Number of minutes to remove
|
||||
int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddMinutes(-minutes);
|
||||
|
||||
continue;
|
||||
}
|
||||
// Check for seconds
|
||||
else if (ago.Contains("Secondes") || ago.Contains("Seconde"))
|
||||
{
|
||||
// Number of seconds to remove
|
||||
int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
|
||||
// Removing
|
||||
release = release.AddSeconds(-seconds);
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Unable to detect release date of torrent", "error");
|
||||
//throw new Exception("Unable to detect release date of torrent");
|
||||
}
|
||||
}
|
||||
return release;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve category ID from media ID
|
||||
/// </summary>
|
||||
/// <param name="media">Media ID</param>
|
||||
/// <returns>Category ID</returns>
|
||||
private string mediaToCategory(string media, string name)
|
||||
{
|
||||
// Declare our Dictionnary -- Media ID (key) <-> Category ID (value)
|
||||
Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
||||
|
||||
// Movies
|
||||
dictionary.Add("565af82b1fd35761568b4573", "565af82b1fd35761568b4572"); // 1080P
|
||||
dictionary.Add("565af82b1fd35761568b4575", "565af82b1fd35761568b4574"); // 720P
|
||||
dictionary.Add("565af82b1fd35761568b4577", "565af82b1fd35761568b4576"); // HDTV
|
||||
dictionary.Add("565af82b1fd35761568b4579", "565af82b1fd35761568b4578"); // Bluray
|
||||
dictionary.Add("565af82b1fd35761568b457b", "565af82b1fd35761568b457a"); // Bluray Remux
|
||||
dictionary.Add("565af82b1fd35761568b457d", "565af82b1fd35761568b457c"); // Bluray 3D
|
||||
|
||||
// TV
|
||||
dictionary.Add("565af82d1fd35761568b4588", "565af82d1fd35761568b4587"); // 1080P
|
||||
dictionary.Add("565af82d1fd35761568b458a", "565af82d1fd35761568b4589"); // 720P
|
||||
dictionary.Add("565af82d1fd35761568b458c", "565af82d1fd35761568b458b"); // HDTV
|
||||
dictionary.Add("565af82d1fd35761568b458e", "565af82d1fd35761568b458d"); // Bluray
|
||||
dictionary.Add("565af82d1fd35761568b4590", "565af82d1fd35761568b458f"); // Bluray Remux
|
||||
dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b4591"); // Bluray 3D
|
||||
|
||||
// Anime
|
||||
dictionary.Add("565af82d1fd35761568b459d", "565af82d1fd35761568b459c"); // 1080P
|
||||
dictionary.Add("565af82d1fd35761568b459f", "565af82d1fd35761568b459e"); // 720P
|
||||
dictionary.Add("565af82d1fd35761568b45a1", "565af82d1fd35761568b45a0"); // HDTV
|
||||
dictionary.Add("565af82d1fd35761568b45a3", "565af82d1fd35761568b45a2"); // Bluray
|
||||
dictionary.Add("565af82d1fd35761568b45a5", "565af82d1fd35761568b45a4"); // Bluray Remux
|
||||
// BUG ~~ Media ID for Anime BR 3D is same as TV BR 3D ~~
|
||||
//dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b45a6"); // Bluray 3D
|
||||
|
||||
// Other
|
||||
dictionary.Add("565af82d1fd35761568b45b0", "565af82d1fd35761568b45af"); // Apps
|
||||
dictionary.Add("565af82d1fd35761568b45b2", "565af82d1fd35761568b45b1"); // Clips
|
||||
dictionary.Add("565af82d1fd35761568b45b4", "565af82d1fd35761568b45b3"); // Audios Tracks of Movies/TV/Anime
|
||||
dictionary.Add("565af82d1fd35761568b45b6", "565af82d1fd35761568b45b5"); // Documentary
|
||||
dictionary.Add("565af82d1fd35761568b45b8", "565af82d1fd35761568b45b7"); // Bluray (ALL)
|
||||
|
||||
// Check if we know this media ID
|
||||
if (dictionary.ContainsKey(media))
|
||||
{
|
||||
// Due to a bug on tracker side, check for a specific id/name as image is same for TV/Anime BR 3D
|
||||
if(media == "565af82d1fd35761568b4592" && name == "Animations - Bluray 3D")
|
||||
{
|
||||
// If it's an Anime BR 3D
|
||||
return "565af82d1fd35761568b45a6";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return category ID for media ID
|
||||
return dictionary[media];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Media ID unknown
|
||||
throw new Exception("Media ID Unknow !");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output message for logging or developpment (console)
|
||||
/// </summary>
|
||||
/// <param name="message">Message to output</param>
|
||||
/// <param name="level">Level for Logger</param>
|
||||
private void output(string message, string level = "debug")
|
||||
{
|
||||
// Check if we are in dev mode
|
||||
if(DevMode)
|
||||
{
|
||||
// Output message to console
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send message to logger with level
|
||||
switch (level)
|
||||
{
|
||||
default:
|
||||
goto case "debug";
|
||||
case "debug":
|
||||
// Only if Debug Level Enabled on Jackett
|
||||
if(Engine.Logger.IsDebugEnabled)
|
||||
{
|
||||
logger.Debug(message);
|
||||
}
|
||||
break;
|
||||
case "info":
|
||||
logger.Info(message);
|
||||
break;
|
||||
case "error":
|
||||
logger.Error(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate Config entered by user on Jackett
|
||||
/// </summary>
|
||||
private void validateConfig()
|
||||
{
|
||||
output("\nValidating Settings ... \n");
|
||||
|
||||
// Check Username Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Username.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Username (auth) => " + ConfigData.Username.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Password Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.Password.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- Password (auth) => " + ConfigData.Password.Value.ToString());
|
||||
}
|
||||
|
||||
// Check Max Page Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency Setting
|
||||
if (ConfigData.Latency.Value)
|
||||
{
|
||||
output("\nValidated Setting -- Latency Simulation enabled");
|
||||
|
||||
// Check Latency Start Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Latency End Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Browser Setting
|
||||
if (ConfigData.Browser.Value)
|
||||
{
|
||||
output("\nValidated Setting -- Browser Simulation enabled");
|
||||
|
||||
// Check ACCEPT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT (header) => " + ConfigData.HeaderAccept.Value.ToString());
|
||||
}
|
||||
|
||||
// Check ACCEPT-LANG header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- ACCEPT-LANG (header) => " + ConfigData.HeaderAcceptLang.Value.ToString());
|
||||
}
|
||||
|
||||
// Check USER-AGENT header Setting
|
||||
if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
|
||||
{
|
||||
throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
|
||||
}
|
||||
else
|
||||
{
|
||||
output("Validated Setting -- USER-AGENT (header) => " + ConfigData.HeaderUserAgent.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Check Dev Cache Settings
|
||||
if (ConfigData.HardDriveCache.Value == true)
|
||||
{
|
||||
output("\nValidated Setting -- DEV Hard Drive Cache enabled");
|
||||
|
||||
// Check if Dev Mode enabled !
|
||||
if (!ConfigData.DevMode.Value)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive is enabled but not in DEV MODE, Please enable DEV MODE !", ConfigData);
|
||||
}
|
||||
|
||||
// Check Cache Keep Time Setting
|
||||
if (!string.IsNullOrEmpty(ConfigData.HardDriveCacheKeepTime.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
output("Validated Setting -- Cache Keep Time (ms) => " + Convert.ToInt32(ConfigData.HardDriveCacheKeepTime.Value));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ExceptionWithConfigData("Please enter a numeric hard drive keep time in ms !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExceptionWithConfigData("Hard Drive Cache enabled, Please enter a maximum keep time for cache !", ConfigData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete cache if previously existed
|
||||
cleanCacheStorage(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
273
src/Jackett/Indexers/XSpeeds.cs
Normal file
273
src/Jackett/Indexers/XSpeeds.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
using CsQuery;
|
||||
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.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Jackett.Models.IndexerConfig;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Jackett.Indexers
|
||||
{
|
||||
public class XSpeeds : BaseIndexer, IIndexer
|
||||
{
|
||||
string LoginUrl { get { return SiteLink + "takelogin.php"; } }
|
||||
string GetRSSKeyUrl { get { return SiteLink + "getrss.php"; } }
|
||||
string SearchUrl { get { return SiteLink + "browse.php"; } }
|
||||
string RSSUrl { get { return SiteLink + "rss.php?secret_key={0}&feedtype=download&timezone=0&showrows=50&categories=all"; } }
|
||||
string CommentUrl { get { return SiteLink + "details.php?id={0}"; } }
|
||||
string DownloadUrl { get { return SiteLink + "download.php?id={0}"; } }
|
||||
|
||||
new ConfigurationDataBasicLoginWithRSSAndDisplay configData
|
||||
{
|
||||
get {return (ConfigurationDataBasicLoginWithRSSAndDisplay)base.configData; }
|
||||
set { base.configData = value; }
|
||||
}
|
||||
|
||||
public XSpeeds(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
|
||||
: base(name: "XSpeeds",
|
||||
description: "XSpeeds",
|
||||
link: "https://www.xspeeds.eu/",
|
||||
caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
|
||||
manager: i,
|
||||
client: wc,
|
||||
logger: l,
|
||||
p: ps,
|
||||
configData: new ConfigurationDataBasicLoginWithRSSAndDisplay())
|
||||
{
|
||||
this.configData.DisplayText.Value = "Expect an initial delay (often around 10 seconds) due to XSpeeds CloudFlare DDoS protection";
|
||||
this.configData.DisplayText.Name = "Notice";
|
||||
AddCategoryMapping(70, TorznabCatType.TVAnime);
|
||||
AddCategoryMapping(80, TorznabCatType.AudioAudiobook);
|
||||
AddCategoryMapping(66, TorznabCatType.MoviesBluRay);
|
||||
AddCategoryMapping(48, TorznabCatType.Books);
|
||||
AddCategoryMapping(68, TorznabCatType.MoviesOther);
|
||||
AddCategoryMapping(65, TorznabCatType.TVDocumentary);
|
||||
AddCategoryMapping(10, TorznabCatType.MoviesDVD);
|
||||
AddCategoryMapping(74, TorznabCatType.TVOTHER);
|
||||
AddCategoryMapping(44, TorznabCatType.TVSport);
|
||||
AddCategoryMapping(12, TorznabCatType.Movies);
|
||||
AddCategoryMapping(13, TorznabCatType.Audio);
|
||||
AddCategoryMapping(6, TorznabCatType.PC);
|
||||
AddCategoryMapping(4, TorznabCatType.PC);
|
||||
AddCategoryMapping(31, TorznabCatType.ConsolePS3);
|
||||
AddCategoryMapping(31, TorznabCatType.ConsolePS4);
|
||||
AddCategoryMapping(20, TorznabCatType.TVSport);
|
||||
AddCategoryMapping(86, TorznabCatType.TVSport);
|
||||
AddCategoryMapping(47, TorznabCatType.TVHD);
|
||||
AddCategoryMapping(16, TorznabCatType.TVSD);
|
||||
AddCategoryMapping(7, TorznabCatType.ConsoleWii);
|
||||
AddCategoryMapping(8, TorznabCatType.ConsoleXbox);
|
||||
|
||||
// RSS Textual categories
|
||||
AddCategoryMapping("Apps", TorznabCatType.PC);
|
||||
AddCategoryMapping("Music", TorznabCatType.Audio);
|
||||
AddCategoryMapping("Audiobooks", TorznabCatType.AudioAudiobook);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
|
||||
{
|
||||
configData.LoadValuesFromJson(configJson);
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value }
|
||||
};
|
||||
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink, true);
|
||||
result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink,true);
|
||||
await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
|
||||
{
|
||||
CQ dom = result.Content;
|
||||
var errorMessage = dom[".left_side table:eq(0) tr:eq(1)"].Text().Trim().Replace("\n\t", " ");
|
||||
throw new ExceptionWithConfigData(errorMessage, configData);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
// Get RSS key
|
||||
var rssParams = new Dictionary<string, string> {
|
||||
{ "feedtype", "download" },
|
||||
{ "timezone", "0" },
|
||||
{ "showrows", "50" }
|
||||
};
|
||||
var rssPage = await PostDataWithCookies(GetRSSKeyUrl, rssParams, result.Cookies);
|
||||
var match = Regex.Match(rssPage.Content, "(?<=secret_key\\=)([a-zA-z0-9]*)");
|
||||
configData.RSSKey.Value = match.Success ? match.Value : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(configData.RSSKey.Value))
|
||||
throw new Exception("Failed to get RSS Key");
|
||||
SaveConfig();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
IsConfigured = false;
|
||||
throw e;
|
||||
}
|
||||
return IndexerConfigurationStatus.RequiresTesting;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
var searchString = query.GetQueryString();
|
||||
var prevCook = CookieHeader + "";
|
||||
|
||||
// If we have no query use the RSS Page as their server is slow enough at times!
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
{
|
||||
var rssPage = await RequestStringWithCookiesAndRetry(string.Format(RSSUrl, configData.RSSKey.Value));
|
||||
if (rssPage.Content.EndsWith("\0")) {
|
||||
rssPage.Content = rssPage.Content.Substring(0, rssPage.Content.Length - 1);
|
||||
}
|
||||
var rssDoc = XDocument.Parse(rssPage.Content);
|
||||
|
||||
foreach (var item in rssDoc.Descendants("item"))
|
||||
{
|
||||
var title = item.Descendants("title").First().Value;
|
||||
var description = item.Descendants("description").First().Value;
|
||||
var link = item.Descendants("link").First().Value;
|
||||
var category = item.Descendants("category").First().Value;
|
||||
var date = item.Descendants("pubDate").First().Value;
|
||||
|
||||
var torrentIdMatch = Regex.Match(link, "(?<=id=)(\\d)*");
|
||||
var torrentId = torrentIdMatch.Success ? torrentIdMatch.Value : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(torrentId))
|
||||
throw new Exception("Missing torrent id");
|
||||
|
||||
var infoMatch = Regex.Match(description, @"Category:\W(?<cat>.*)\W\/\WSeeders:\W(?<seeders>\d*)\W\/\WLeechers:\W(?<leechers>\d*)\W\/\WSize:\W(?<size>[\d\.]*\W\S*)");
|
||||
if (!infoMatch.Success)
|
||||
throw new Exception("Unable to find info");
|
||||
|
||||
var release = new ReleaseInfo()
|
||||
{
|
||||
Title = title,
|
||||
Description = title,
|
||||
Guid = new Uri(string.Format(DownloadUrl, torrentId)),
|
||||
Comments = new Uri(string.Format(CommentUrl, torrentId)),
|
||||
PublishDate = DateTime.ParseExact(date, "yyyy-MM-dd H:mm:ss", CultureInfo.InvariantCulture), //2015-08-08 21:20:31
|
||||
Link = new Uri(string.Format(DownloadUrl, torrentId)),
|
||||
Seeders = ParseUtil.CoerceInt(infoMatch.Groups["seeders"].Value),
|
||||
Peers = ParseUtil.CoerceInt(infoMatch.Groups["leechers"].Value),
|
||||
Size = ReleaseInfo.GetBytes(infoMatch.Groups["size"].Value),
|
||||
Category = MapTrackerCatToNewznab(infoMatch.Groups["cat"].Value)
|
||||
};
|
||||
|
||||
// If its not apps or audio we can only mark as general TV
|
||||
if (release.Category == 0)
|
||||
release.Category = 5030;
|
||||
|
||||
release.Peers += release.Seeders;
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (searchString.Length < 3)
|
||||
{
|
||||
OnParseError("", new Exception("Minimum search length is 3"));
|
||||
return releases;
|
||||
}
|
||||
var searchParams = new Dictionary<string, string> {
|
||||
{ "do", "search" },
|
||||
{ "keywords", searchString },
|
||||
{ "search_type", "t_name" },
|
||||
{ "category", "0" },
|
||||
{ "include_dead_torrents", "no" }
|
||||
};
|
||||
var pairs = new Dictionary<string, string> {
|
||||
{ "username", configData.Username.Value },
|
||||
{ "password", configData.Password.Value }
|
||||
};
|
||||
var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, this.CookieHeader, true, null, SiteLink, true);
|
||||
if (!result.Cookies.Trim().Equals(prevCook.Trim()))
|
||||
{
|
||||
result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink, true);
|
||||
}
|
||||
this.CookieHeader = result.Cookies;
|
||||
|
||||
var attempt = 1;
|
||||
var searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams,this.CookieHeader);
|
||||
while (searchPage.IsRedirect && attempt < 3)
|
||||
{
|
||||
// add any cookies
|
||||
var cookieString = this.CookieHeader;
|
||||
if (searchPage.Cookies != null)
|
||||
{
|
||||
cookieString += " " + searchPage.Cookies;
|
||||
// resolve cookie conflicts - really no need for this as the webclient will handle it
|
||||
System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\s]+)=([^=]+)(?:\s|$)");
|
||||
Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
|
||||
var matches = expression.Match(cookieString);
|
||||
while (matches.Success)
|
||||
{
|
||||
if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
|
||||
matches = matches.NextMatch();
|
||||
}
|
||||
cookieString = string.Join(" ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
|
||||
}
|
||||
this.CookieHeader = cookieString;
|
||||
attempt++;
|
||||
searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams, this.CookieHeader);
|
||||
}
|
||||
try
|
||||
{
|
||||
CQ dom = searchPage.Content;
|
||||
var rows = dom["#listtorrents tbody tr"];
|
||||
foreach (var row in rows.Skip(1))
|
||||
{
|
||||
var release = new ReleaseInfo();
|
||||
var qRow = row.Cq();
|
||||
|
||||
release.Title = qRow.Find("td:eq(1) .tooltip-content div:eq(0)").Text();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(release.Title))
|
||||
continue;
|
||||
|
||||
release.Description = release.Title;
|
||||
release.Guid = new Uri(qRow.Find("td:eq(2) a").Attr("href"));
|
||||
release.Link = release.Guid;
|
||||
release.Comments = new Uri(qRow.Find("td:eq(1) .tooltip-target a").Attr("href"));
|
||||
release.PublishDate = DateTime.ParseExact(qRow.Find("td:eq(1) div").Last().Text().Trim(), "dd-MM-yyyy H:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); //08-08-2015 12:51
|
||||
release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:eq(6)").Text());
|
||||
release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find("td:eq(7)").Text().Trim());
|
||||
release.Size = ReleaseInfo.GetBytes(qRow.Find("td:eq(4)").Text().Trim());
|
||||
|
||||
|
||||
var cat = row.Cq().Find("td:eq(0) a").First().Attr("href");
|
||||
var catSplit = cat.LastIndexOf('=');
|
||||
if (catSplit > -1)
|
||||
cat = cat.Substring(catSplit + 1);
|
||||
release.Category = MapTrackerCatToNewznab(cat);
|
||||
|
||||
// If its not apps or audio we can only mark as general TV
|
||||
if (release.Category == 0)
|
||||
release.Category = 5030;
|
||||
|
||||
releases.Add(release);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnParseError(searchPage.Content, ex);
|
||||
}
|
||||
}
|
||||
if (!CookieHeader.Trim().Equals(prevCook.Trim()))
|
||||
{
|
||||
this.SaveConfig();
|
||||
}
|
||||
return releases;
|
||||
}
|
||||
}
|
||||
}
|
@@ -183,6 +183,7 @@
|
||||
<Compile Include="Controllers\DownloadController.cs" />
|
||||
<Compile Include="Engine.cs" />
|
||||
<Compile Include="Indexers\AlphaRatio.cs" />
|
||||
<Compile Include="Indexers\BitSoup.cs" />
|
||||
<Compile Include="Indexers\BlueTigers.cs" />
|
||||
<Compile Include="Indexers\EuTorrents.cs" />
|
||||
<Compile Include="Indexers\Avistaz.cs" />
|
||||
@@ -194,7 +195,9 @@
|
||||
<Compile Include="Indexers\Demonoid.cs" />
|
||||
<Compile Include="Indexers\BroadcastTheNet.cs" />
|
||||
<Compile Include="Indexers\DanishBits.cs" />
|
||||
<Compile Include="Indexers\Abnormal.cs" />
|
||||
<Compile Include="Indexers\GFTracker.cs" />
|
||||
<Compile Include="Indexers\RevolutionTT.cs" />
|
||||
<Compile Include="Indexers\TehConnection.cs" />
|
||||
<Compile Include="Indexers\Hounddawgs.cs" />
|
||||
<Compile Include="Indexers\Shazbat.cs" />
|
||||
@@ -205,11 +208,18 @@
|
||||
<Compile Include="Indexers\ImmortalSeed.cs" />
|
||||
<Compile Include="Indexers\FileList.cs" />
|
||||
<Compile Include="Indexers\Abstract\AvistazTracker.cs" />
|
||||
<Compile Include="Indexers\FrenchADN.cs" />
|
||||
<Compile Include="Indexers\WiHD.cs" />
|
||||
<Compile Include="Indexers\XSpeeds.cs" />
|
||||
<Compile Include="Models\GitHub\Asset.cs" />
|
||||
<Compile Include="Models\GitHub\Release.cs" />
|
||||
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" />
|
||||
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataAbnormal.cs" />
|
||||
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataFrenchADN.cs" />
|
||||
<Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataWiHD.cs" />
|
||||
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilter.cs" />
|
||||
<Compile Include="Models\IndexerConfig\ConfigurationDataAPIKey.cs" />
|
||||
<Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSSAndDisplay.cs" />
|
||||
<Compile Include="Models\ManualSearchResult.cs" />
|
||||
<Compile Include="Indexers\TVChaosUK.cs" />
|
||||
<Compile Include="Indexers\NCore.cs" />
|
||||
@@ -434,6 +444,9 @@
|
||||
<Content Include="Content\logos\beyondhd.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\bitsoup.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\bluetigers.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -476,6 +489,9 @@
|
||||
<Content Include="Content\logos\privatehd.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\revolutiontt.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\sceneaccess.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -554,6 +570,18 @@
|
||||
<Content Include="Content\logos\tvchaosuk.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\frenchadn.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\abnormal.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\wihd.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\logos\xspeeds.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Content\setup_indexer.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@@ -23,6 +23,7 @@ namespace Jackett.Models.Config
|
||||
public string BlackholeDir { get; set; }
|
||||
public bool UpdateDisabled { get; set; }
|
||||
public bool UpdatePrerelease { get; set; }
|
||||
public string BasePathOverride { get; set; }
|
||||
|
||||
public string[] GetListenAddresses(bool? external = null)
|
||||
{
|
||||
|
@@ -0,0 +1,57 @@
|
||||
namespace Jackett.Models.IndexerConfig.Bespoke
|
||||
{
|
||||
class ConfigurationDataAbnormal : ConfigurationData
|
||||
{
|
||||
public HiddenItem AuthKey { get; set; }
|
||||
public HiddenItem TorrentPass { get; set; }
|
||||
public DisplayItem CredentialsWarning { get; private set; }
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public DisplayItem PagesWarning { get; private set; }
|
||||
public StringItem Pages { get; private set; }
|
||||
public DisplayItem SecurityWarning { get; private set; }
|
||||
public BoolItem Latency { get; private set; }
|
||||
public BoolItem Browser { get; private set; }
|
||||
public DisplayItem LatencyWarning { get; private set; }
|
||||
public StringItem LatencyStart { get; private set; }
|
||||
public StringItem LatencyEnd { get; private set; }
|
||||
public DisplayItem HeadersWarning { get; private set; }
|
||||
public StringItem HeaderAccept { get; private set; }
|
||||
public StringItem HeaderAcceptLang { get; private set; }
|
||||
public BoolItem HeaderDNT { get; private set; }
|
||||
public BoolItem HeaderUpgradeInsecure { get; private set; }
|
||||
public StringItem HeaderUserAgent { get; private set; }
|
||||
public DisplayItem DevWarning { get; private set; }
|
||||
public BoolItem DevMode { get; private set; }
|
||||
public BoolItem HardDriveCache { get; private set; }
|
||||
public StringItem HardDriveCacheKeepTime { get; private set; }
|
||||
|
||||
public ConfigurationDataAbnormal()
|
||||
: base()
|
||||
{
|
||||
AuthKey = new HiddenItem { Name = "AuthKey", Value = "" };
|
||||
TorrentPass = new HiddenItem { Name = "TorrentPass", Value = "" };
|
||||
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
|
||||
Username = new StringItem { Name = "Username (Required)", Value = "" };
|
||||
Password = new StringItem { Name = "Password (Required)", Value = "" };
|
||||
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>With Dead Torrents</b> let you search <u>with</u> torrents which are marked Dead.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>With Nuked Releases</b> let you search <u>with</u> torrents which are marked Nuked.</li><li><b>3D Releases Only</b> let you search <u>only</u> for torrents which are marked 3D.</li><li><b>French Only</b> let you search <u>only</u> for torrents which are marked FRENCH or TRUEFRENCH (<i>MULTI not included !</i>).</li></ul>") { Name = "Preferences" };
|
||||
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
|
||||
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
|
||||
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
|
||||
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
|
||||
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
|
||||
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
|
||||
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
|
||||
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
|
||||
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
|
||||
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
|
||||
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
|
||||
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
|
||||
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
|
||||
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
|
||||
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
|
||||
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
|
||||
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,12 +11,14 @@ namespace Jackett.Models.IndexerConfig.Bespoke
|
||||
{
|
||||
public BoolItem IncludeRaw { get; private set; }
|
||||
public DisplayItem DateWarning { get; private set; }
|
||||
public BoolItem InsertSeason { get; private set; }
|
||||
|
||||
public ConfigurationDataAnimeBytes()
|
||||
: base()
|
||||
{
|
||||
IncludeRaw = new BoolItem() { Name = "IncludeRaw", Value = false };
|
||||
DateWarning = new DisplayItem("This tracker does not supply upload dates so they are based off year of release.") { Name = "DateWarning" };
|
||||
InsertSeason = new BoolItem() { Name = "Prefix episode number with S01 for Sonarr Compatability", Value = false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,53 @@
|
||||
namespace Jackett.Models.IndexerConfig.Bespoke
|
||||
{
|
||||
class ConfigurationDataFrenchADN : ConfigurationData
|
||||
{
|
||||
public DisplayItem CredentialsWarning { get; private set; }
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public DisplayItem PagesWarning { get; private set; }
|
||||
public StringItem Pages { get; private set; }
|
||||
public DisplayItem SecurityWarning { get; private set; }
|
||||
public BoolItem Latency { get; private set; }
|
||||
public BoolItem Browser { get; private set; }
|
||||
public DisplayItem LatencyWarning { get; private set; }
|
||||
public StringItem LatencyStart { get; private set; }
|
||||
public StringItem LatencyEnd { get; private set; }
|
||||
public DisplayItem HeadersWarning { get; private set; }
|
||||
public StringItem HeaderAccept { get; private set; }
|
||||
public StringItem HeaderAcceptLang { get; private set; }
|
||||
public BoolItem HeaderDNT { get; private set; }
|
||||
public BoolItem HeaderUpgradeInsecure { get; private set; }
|
||||
public StringItem HeaderUserAgent { get; private set; }
|
||||
public DisplayItem DevWarning { get; private set; }
|
||||
public BoolItem DevMode { get; private set; }
|
||||
public BoolItem HardDriveCache { get; private set; }
|
||||
public StringItem HardDriveCacheKeepTime { get; private set; }
|
||||
|
||||
public ConfigurationDataFrenchADN()
|
||||
: base()
|
||||
{
|
||||
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
|
||||
Username = new StringItem { Name = "Username (Required)", Value = "" };
|
||||
Password = new StringItem { Name = "Password (Required)", Value = "" };
|
||||
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li></ul>") { Name = "Preferences" };
|
||||
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
|
||||
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.<b>You must enable it to use this provider!</b></li></ul>") { Name = "Security" };
|
||||
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = false };
|
||||
Browser = new BoolItem() { Name = "Browser Simulation (Forced)", Value = true };
|
||||
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
|
||||
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
|
||||
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
|
||||
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
|
||||
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
|
||||
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
|
||||
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
|
||||
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
|
||||
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
|
||||
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
|
||||
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
|
||||
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
|
||||
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
namespace Jackett.Models.IndexerConfig.Bespoke
|
||||
{
|
||||
class ConfigurationDataWiHD : ConfigurationData
|
||||
{
|
||||
public DisplayItem CredentialsWarning { get; private set; }
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public DisplayItem PagesWarning { get; private set; }
|
||||
public StringItem Pages { get; private set; }
|
||||
public BoolItem Exclusive { get; private set; }
|
||||
public BoolItem Freeleech { get; private set; }
|
||||
public BoolItem Reseed { get; private set; }
|
||||
public DisplayItem SecurityWarning { get; private set; }
|
||||
public BoolItem Latency { get; private set; }
|
||||
public BoolItem Browser { get; private set; }
|
||||
public DisplayItem LatencyWarning { get; private set; }
|
||||
public StringItem LatencyStart { get; private set; }
|
||||
public StringItem LatencyEnd { get; private set; }
|
||||
public DisplayItem HeadersWarning { get; private set; }
|
||||
public StringItem HeaderAccept { get; private set; }
|
||||
public StringItem HeaderAcceptLang { get; private set; }
|
||||
public BoolItem HeaderDNT { get; private set; }
|
||||
public BoolItem HeaderUpgradeInsecure { get; private set; }
|
||||
public StringItem HeaderUserAgent { get; private set; }
|
||||
public DisplayItem DevWarning { get; private set; }
|
||||
public BoolItem DevMode { get; private set; }
|
||||
public BoolItem HardDriveCache { get; private set; }
|
||||
public StringItem HardDriveCacheKeepTime { get; private set; }
|
||||
|
||||
public ConfigurationDataWiHD()
|
||||
: base()
|
||||
{
|
||||
CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
|
||||
Username = new StringItem { Name = "Username (Required)", Value = "" };
|
||||
Password = new StringItem { Name = "Password (Required)", Value = "" };
|
||||
PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name = "Preferences" };
|
||||
Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
|
||||
Exclusive = new BoolItem() { Name = "Exclusive Only (Optional)", Value = false };
|
||||
Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false };
|
||||
Reseed = new BoolItem() { Name = "Reseed Needed Only (Optional)", Value = false };
|
||||
SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
|
||||
Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = true };
|
||||
Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
|
||||
LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
|
||||
LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
|
||||
LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
|
||||
HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
|
||||
HeaderAccept = new StringItem { Name = "Accept", Value = "" };
|
||||
HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
|
||||
HeaderDNT = new BoolItem { Name = "DNT", Value = false };
|
||||
HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
|
||||
HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
|
||||
DevWarning = new DisplayItem("<b>Development Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling development mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li><li>By enabling Hard Drive Cache, <b>This provider</b> will <u>save each query answers from tracker</u> in temp directory, in fact this reduce drastically HTTP requests when building a provider at parsing step for example. So, <b> Jackett will search for a cached query answer on hard drive before executing query on tracker side !</b> <i>DEV MODE must be enabled to use it !</li></ul>") { Name = "Development" };
|
||||
DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
|
||||
HardDriveCache = new BoolItem { Name = "Enable HARD DRIVE CACHE (Developers ONLY)", Value = false };
|
||||
HardDriveCacheKeepTime = new StringItem { Name = "Keep Cached files for (ms)", Value = "300000" };
|
||||
}
|
||||
}
|
||||
}
|
@@ -132,7 +132,7 @@ namespace Jackett.Models.IndexerConfig
|
||||
if (!forDisplay)
|
||||
{
|
||||
properties = properties
|
||||
.Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha)
|
||||
.Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha || p.ItemType == ItemType.DisplayInfo)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,25 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Models.IndexerConfig
|
||||
{
|
||||
public class ConfigurationDataBasicLoginWithRSSAndDisplay : ConfigurationData
|
||||
{
|
||||
public StringItem Username { get; private set; }
|
||||
public StringItem Password { get; private set; }
|
||||
public HiddenItem RSSKey { get; private set; }
|
||||
public DisplayItem DisplayText { get; private set; }
|
||||
|
||||
public ConfigurationDataBasicLoginWithRSSAndDisplay()
|
||||
{
|
||||
Username = new StringItem { Name = "Username" };
|
||||
Password = new StringItem { Name = "Password" };
|
||||
RSSKey = new HiddenItem { Name = "RSSKey" };
|
||||
DisplayText = new DisplayItem(""){ Name = "" };
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
@@ -32,7 +33,8 @@ namespace Jackett.Services
|
||||
void ReserveUrls(bool doInstall = true);
|
||||
ServerConfig Config { get; }
|
||||
void SaveConfig();
|
||||
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
|
||||
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
|
||||
string BasePath();
|
||||
}
|
||||
|
||||
public class ServerService : IServerService
|
||||
@@ -77,6 +79,23 @@ namespace Jackett.Services
|
||||
return new Uri(proxyLink);
|
||||
}
|
||||
|
||||
public string BasePath()
|
||||
{
|
||||
if (config.BasePathOverride == null || config.BasePathOverride == "") {
|
||||
return "/";
|
||||
}
|
||||
var path = config.BasePathOverride;
|
||||
if (!path.EndsWith("/"))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
if (!path.StartsWith("/"))
|
||||
{
|
||||
path = "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
// Load config
|
||||
@@ -139,6 +158,7 @@ namespace Jackett.Services
|
||||
logger.Info("Starting web server at " + config.GetListenAddresses()[0]);
|
||||
var startOptions = new StartOptions();
|
||||
config.GetListenAddresses().ToList().ForEach(u => startOptions.Urls.Add(u));
|
||||
Startup.BasePath = BasePath();
|
||||
_server = WebApp.Start<Startup>(startOptions);
|
||||
logger.Debug("Web server started");
|
||||
updater.StartUpdateChecker();
|
||||
|
@@ -109,6 +109,11 @@ namespace Jackett.Services
|
||||
|
||||
var releases = JsonConvert.DeserializeObject<List<Release>>(response.Content);
|
||||
|
||||
if (!config.UpdatePrerelease)
|
||||
{
|
||||
releases = releases.Where(r => !r.Prerelease).ToList();
|
||||
}
|
||||
|
||||
if (releases.Count > 0)
|
||||
{
|
||||
var latestRelease = releases.OrderByDescending(o => o.Created_at).First();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using Owin;
|
||||
using Owin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -40,6 +40,12 @@ namespace Jackett
|
||||
set;
|
||||
}
|
||||
|
||||
public static string ProxyConnection
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static bool? DoSSLFix
|
||||
{
|
||||
get;
|
||||
@@ -52,6 +58,12 @@ namespace Jackett
|
||||
set;
|
||||
}
|
||||
|
||||
public static string BasePath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void Configuration(IAppBuilder appBuilder)
|
||||
{
|
||||
// Configure Web API for self-host.
|
||||
|
@@ -13,11 +13,11 @@ namespace Jackett.Utils
|
||||
get {
|
||||
if (System.Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
{
|
||||
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chrome/45.0.2454.101 Safari/537.36";
|
||||
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chrome/47.0.2526.73 Safari/537.36";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36";
|
||||
return "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -62,18 +62,28 @@ namespace Jackett.Utils.Clients
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var useProxy = false;
|
||||
WebProxy proxyServer = null;
|
||||
if (Startup.ProxyConnection != null)
|
||||
{
|
||||
proxyServer = new WebProxy(Startup.ProxyConnection, false);
|
||||
useProxy = true;
|
||||
}
|
||||
var client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
CookieContainer = cookies,
|
||||
AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more.
|
||||
UseCookies = true,
|
||||
Proxy = proxyServer,
|
||||
UseProxy = useProxy
|
||||
});
|
||||
|
||||
|
||||
if(webRequest.EmulateBrowser)
|
||||
client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
|
||||
if (webRequest.EmulateBrowser)
|
||||
client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
|
||||
else
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
|
||||
|
||||
HttpResponseMessage response = null;
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.ExpectContinue = false;
|
||||
@@ -118,6 +128,35 @@ namespace Jackett.Utils.Clients
|
||||
|
||||
var result = new WebClientByteResult();
|
||||
result.Content = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
// some cloudflare clients are using a refresh header
|
||||
// Pull it out manually
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable && response.Headers.Contains("Refresh"))
|
||||
{
|
||||
var refreshHeaders = response.Headers.GetValues("Refresh");
|
||||
var redirval = "";
|
||||
var redirtime = 0;
|
||||
if (refreshHeaders != null)
|
||||
{
|
||||
foreach (var value in refreshHeaders)
|
||||
{
|
||||
var start = value.IndexOf("=");
|
||||
var end = value.IndexOf(";");
|
||||
var len = value.Length;
|
||||
if (start > -1)
|
||||
{
|
||||
redirval = value.Substring(start + 1);
|
||||
result.RedirectingTo = redirval;
|
||||
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
|
||||
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
|
||||
// it shoudln't include service unavailable..only if we have this redirect header.
|
||||
response.StatusCode = System.Net.HttpStatusCode.Redirect;
|
||||
redirtime = Int32.Parse(value.Substring(0, end));
|
||||
System.Threading.Thread.Sleep(redirtime * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.Headers.Location != null)
|
||||
{
|
||||
result.RedirectingTo = response.Headers.Location.ToString();
|
||||
@@ -129,6 +168,7 @@ namespace Jackett.Utils.Clients
|
||||
// http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer
|
||||
IEnumerable<string> cookieHeaders;
|
||||
var responseCookies = new List<Tuple<string, string>>();
|
||||
|
||||
if (response.Headers.TryGetValues("set-cookie", out cookieHeaders))
|
||||
{
|
||||
foreach (var value in cookieHeaders)
|
||||
|
@@ -104,6 +104,28 @@ namespace Jackett.Utils.Clients
|
||||
case "location":
|
||||
result.RedirectingTo = header[1];
|
||||
break;
|
||||
case "refresh":
|
||||
if (response.Status == System.Net.HttpStatusCode.ServiceUnavailable)
|
||||
{
|
||||
//"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
|
||||
var redirval = "";
|
||||
var value = header[1];
|
||||
var start = value.IndexOf("=");
|
||||
var end = value.IndexOf(";");
|
||||
var len = value.Length;
|
||||
if (start > -1)
|
||||
{
|
||||
redirval = value.Substring(start + 1);
|
||||
result.RedirectingTo = redirval;
|
||||
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
|
||||
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
|
||||
// it shoudln't include service unavailable..only if we have this redirect header.
|
||||
result.Status = System.Net.HttpStatusCode.Redirect;
|
||||
var redirtime = Int32.Parse(value.Substring(0, end));
|
||||
System.Threading.Thread.Sleep(redirtime * 1000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -50,6 +50,11 @@ namespace Jackett.Utils.Clients
|
||||
private async Task<WebClientByteResult> Run(WebRequest request)
|
||||
{
|
||||
var args = new StringBuilder();
|
||||
if (Startup.ProxyConnection != null)
|
||||
{
|
||||
args.AppendFormat("-x " + Startup.ProxyConnection + " ");
|
||||
}
|
||||
|
||||
args.AppendFormat("--url \"{0}\" ", request.Url);
|
||||
|
||||
if (request.EmulateBrowser)
|
||||
@@ -86,11 +91,16 @@ namespace Jackett.Utils.Clients
|
||||
// https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html
|
||||
args.Append("--cipher " + SSLFix.CipherList);
|
||||
}
|
||||
|
||||
if (Startup.IgnoreSslErrors == true)
|
||||
{
|
||||
args.Append("-k ");
|
||||
}
|
||||
args.Append("-H \"Accept-Language: en-US,en\" ");
|
||||
args.Append("-H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\" ");
|
||||
string stdout = null;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString(), true);
|
||||
stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString() , true);
|
||||
});
|
||||
|
||||
var outputData = File.ReadAllBytes(tempFile);
|
||||
@@ -101,6 +111,16 @@ namespace Jackett.Utils.Clients
|
||||
if (headSplit < 0)
|
||||
throw new Exception("Invalid response");
|
||||
var headers = stdout.Substring(0, headSplit);
|
||||
if (Startup.ProxyConnection != null)
|
||||
{
|
||||
// the proxy provided headers too so we need to split headers again
|
||||
var headSplit1 = stdout.IndexOf("\r\n\r\n",headSplit + 4);
|
||||
if (headSplit1 > 0)
|
||||
{
|
||||
headers = stdout.Substring(headSplit + 4,headSplit1 - (headSplit + 4));
|
||||
headSplit = headSplit1;
|
||||
}
|
||||
}
|
||||
var headerCount = 0;
|
||||
var cookieBuilder = new StringBuilder();
|
||||
var cookies = new List<Tuple<string, string>>();
|
||||
@@ -131,6 +151,24 @@ namespace Jackett.Utils.Clients
|
||||
case "location":
|
||||
result.RedirectingTo = value.Trim();
|
||||
break;
|
||||
case "refresh":
|
||||
//"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
|
||||
var redirval = "";
|
||||
var start = value.IndexOf("=");
|
||||
var end = value.IndexOf(";");
|
||||
var len = value.Length;
|
||||
if (start > -1)
|
||||
{
|
||||
redirval = value.Substring(start + 1);
|
||||
result.RedirectingTo = redirval;
|
||||
// normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
|
||||
// of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
|
||||
// it shoudln't include service unavailable..only if we have this redirect header.
|
||||
result.Status = System.Net.HttpStatusCode.Redirect;
|
||||
var redirtime = Int32.Parse(value.Substring(0, end));
|
||||
System.Threading.Thread.Sleep(redirtime * 1000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +1,42 @@
|
||||
using Microsoft.Owin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public class WebApiRootRedirectMiddleware : OwinMiddleware
|
||||
{
|
||||
public WebApiRootRedirectMiddleware(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task Invoke(IOwinContext context)
|
||||
{
|
||||
var url = context.Request.Uri;
|
||||
if (string.IsNullOrWhiteSpace(url.AbsolutePath) || url.AbsolutePath == "/")
|
||||
{
|
||||
// 301 is the status code of permanent redirect
|
||||
context.Response.StatusCode = 301;
|
||||
context.Response.Headers.Set("Location", "/Admin/Dashboard");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Jackett.Services;
|
||||
using Microsoft.Owin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Jackett.Utils
|
||||
{
|
||||
public class WebApiRootRedirectMiddleware : OwinMiddleware
|
||||
{
|
||||
public WebApiRootRedirectMiddleware(OwinMiddleware next)
|
||||
: base(next)
|
||||
{
|
||||
}
|
||||
|
||||
public async override Task Invoke(IOwinContext context)
|
||||
{
|
||||
var url = context.Request.Uri;
|
||||
if(context.Request.Path != null && context.Request.Path.HasValue && context.Request.Path.Value.StartsWith(Startup.BasePath))
|
||||
{
|
||||
context.Request.Path = new PathString(context.Request.Path.Value.Substring(Startup.BasePath.Length-1));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(url.AbsolutePath) || url.AbsolutePath == "/")
|
||||
{
|
||||
// 301 is the status code of permanent redirect
|
||||
context.Response.StatusCode = 301;
|
||||
var redir = Startup.BasePath + "Admin/Dashboard";
|
||||
Engine.Logger.Info("redirecting to " + redir);
|
||||
context.Response.Headers.Set("Location", redir);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user