diff --git a/.gitignore b/.gitignore
index 57a1574c4f5ccee529831e15bdf01c373ea5a845..eb5a9e709eafab6f77d6104d3d957674ec578918 100644
--- a/.gitignore
+++ b/.gitignore
@@ -194,3 +194,5 @@ FakesAssemblies/
 
 # Visual Studio 6 workspace options file
 *.opt
+/Build.mono
+/Build.windows
diff --git a/Build.bat b/Build.bat
index 320e860a81477df3b4694a8302337e6c99cf1a5f..7f9b4c60c443f5e9922556a5b6beae9a071e64a8 100644
--- a/Build.bat
+++ b/Build.bat
@@ -1,21 +1,32 @@
 
-rmdir /s /q  build
+rmdir /s /q  build.windows
+rmdir /s /q  build.mono
 rmdir /s /q  Output
 cd src
-Msbuild Jackett.sln /t:Clean,Build /p:Configuration=Release
+Msbuild Jackett.sln /t:Clean,Build /p:Configuration=Release /verbosity:minimal
 cd ..
 
-xcopy src\Jackett.Console\bin\Release Build\  /e /y
-copy /Y src\Jackett.Service\bin\Release\JackettService.exe build\JackettService.exe
-copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build\JackettService.exe.config
-copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build\JackettTray.exe
-copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build\JackettTray.exe.config
-copy /Y LICENSE build\LICENSE
-copy /Y README.md build\README.md
-cd build
-del *.pdb
-del *.xml
+xcopy src\Jackett.Console\bin\Release Build.windows\  /e /y
+copy /Y src\Jackett.Service\bin\Release\JackettService.exe build.windows\JackettService.exe
+copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build.windows\JackettService.exe.config
+copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build.windows\JackettTray.exe
+copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build.windows\JackettTray.exe.config
+copy /Y LICENSE build.windows\LICENSE
+copy /Y README.md build.windows\README.md
+
+
+cd src
+Msbuild Jackett.sln /t:Clean
+call "C:\Program Files (x86)\Mono\bin\xbuild.bat"  Jackett.sln /t:Build /p:Configuration=Release /verbosity:minimal
 cd ..
 
+xcopy src\Jackett.Console\bin\Release Build.mono\  /e /y
+copy /Y src\Jackett.Service\bin\Release\JackettService.exe build.mono\JackettService.exe
+copy /Y src\Jackett.Service\bin\Release\JackettService.exe.config build.mono\JackettService.exe.config
+copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe build.mono\JackettTray.exe
+copy /Y src\Jackett.Tray\bin\Release\JackettTray.exe.config build.mono\JackettTray.exe.config
+copy /Y LICENSE build.mono\LICENSE
+copy /Y README.md build.mono\README.md
+
 iscc Installer.iss
 
diff --git a/Installer.iss b/Installer.iss
index 6733eda5211220c436af5392be25fc2e08b179e2..9a3dc63868f8ea067d7b4f8f1a35922957e82dd3 100644
--- a/Installer.iss
+++ b/Installer.iss
@@ -36,8 +36,8 @@ Name: "windowsService"; Description: "Install as a Windows Service"
 Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
 
 [Files]
-Source: "Build\JackettTray.exe"; DestDir: "{app}"; Flags: ignoreversion
-Source: "Build\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
+Source: "Build.windows\JackettTray.exe"; DestDir: "{app}"; Flags: ignoreversion
+Source: "Build.windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 
 [Icons]
diff --git a/README.md b/README.md
index cf4921a49edd4af8dcda63e6b175430c5f59fb60..7470729cabee1798e6484638630e6612fba362ec 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,21 @@
-# Jackett
+## Jackett
 
-Use just about any tracker with Sonarr
+This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) (with [nZEDb](https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt) category numbering) and [TorrentPotato](https://github.com/RuudBurger/CouchPotatoServer/wiki/Couchpotato-torrent-provider) API server on your machine.  Torznab enables software such as [Sonarr](https://sonarr.tv) to access data from your favorite indexers in a similar fashion to rss but with added features such as searching.  TorrentPotato is an interface accessible to [CouchPotato](https://couchpota.to/).
 
-### API Access to your favorite trackers
 
-This software creates a [Torznab](https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer) API server on your machine that any Torznab enabled software can consume. Jackett works as a proxy server: it translates Torznab queries into tracker-site-specific http queries, parses the html response into Torznab results, then sends results back to the requesting software. 
+Jackett works as a proxy server: it translates Torznab queries into tracker-site-specific http queries, parses the html response into Torznab results, then sends results back to the requesting software which allows for getting recent uploads and performing searches.
 
-Currently [Sonarr](https://sonarr.tv/) is the only software that uses Torznab. [Couchpotato](https://couchpota.to/) will hopefully get Torznab support in the future.
+We were previously focused on TV but are working on extending searches to allow for searching other items such as movies and comics.
 
-### Download
+#### Download
 Download in the [Releases page](https://github.com/zone117x/Jackett/releases)
 
-### Supported Systems
-* Works on Windows by default
-* Works on Linux and OS X using Mono. See instructions below...
+#### Supported Systems
+* Windows using .NET 4.5
+* Linux and OSX using Mono 4
 
-### Instructions for Mono
- * Install Mono: http://www.mono-project.com/download/
- * *For MoreThanTV & ThePirateBay* install libcurl-dev for your system, [tutorial](http://curl.haxx.se/dlwiz/?type=devel)
-   * For apt-get systems its simply: `apt-get install libcurl4-openssl-dev`
 
-### Running Jackett
-
-On Windows the recommended way of running Jackett is to install it as a windows service. When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool.
-
-Jackett can also be run from the command line (See --help for switches) using JackettConsole.exe if you would like to see log messages where the service and tray isn't running. On Linux / OSX you would need to run the console using "mono JackettConsole.exe".
-
-
-### Supported Trackers
+#### Supported Trackers
  * [AlphaRatio](https://alpharatio.cc/)
  * [AnimeBytes](https://animebytes.tv/)
  * [BakaBT](http://bakabt.me/)
@@ -39,29 +27,66 @@ Jackett can also be run from the command line (See --help for switches) using Ja
  * [Freshon](https://freshon.tv/)
  * [HD-Space](https://hd-space.org/)
  * [HD-Torrents.org](https://hd-torrents.org/)
+ * [Immortalseed.me](http://immortalseed.me)
  * [IPTorrents](https://iptorrents.com/)
  * [MoreThan.tv](https://morethan.tv/)
  * [pretome](https://pretome.info)
  * [PrivateHD](https://privatehd.to/)
- * [RARBG](https://rarbg.com)
  * [SceneAccess](https://sceneaccess.eu/login)
  * [SceneTime](https://www.scenetime.com/)
  * [ShowRSS](https://showrss.info/)
  * [Strike](https://getstrike.net/)
  * [T411](http://www.t411.io/)
  * [The Pirate Bay](https://thepiratebay.se/)
+ * [TorrentBytes](https://www.torrentbytes.net/)
  * [TorrentDay](https://torrentday.eu/)
  * [TorrentLeech](http://www.torrentleech.org/)
  * [TorrentShack](http://torrentshack.me/)
  * [Torrentz](https://torrentz.eu/)
 
 
+#### Installation on Linux/OSX
+ 1. Install [Mono 4](http://www.mono-project.com/download/) or better
+ 2. Install  libcurl:
+       * Debian/Ubunutu: apt-get install libcurl-dev
+       * Redhat/Fedora: yum install libcurl-devel
+       * Or see the [Curl docs](http://curl.haxx.se/dlwiz/?type=devel).
+
+
+
+#### Installation on Windows
+
+We recommend you install Jackett as a Windows service using the supplied installer.  When installed as a service the tray icon acts as a way to open/start/stop Jackett. If you opted to not install it as a service then Jackett will run its web server from the tray tool.
+
+Jackett can also be run from the command line using JackettConsole.exe if you would like to see log messages (Ensure the server isn't already running from the tray/service).
+
+#### Installation on Linux/OSX
+
+Run Jackett using mono with the command "mono JackettConsole.exe".
+
+
+
+#### Troubleshooting
+
+* Command line switches
+
+You can pass various options when running via the command line, see --help for details.
+
+* Unable to  connect to certain trackers on Linux
+
+Try running with the "--SSLFix true" if you are on Redhat/Fedora/NNS based libcurl.  If the tracker is currently configured try removing it and adding it again. Alternatively try running with a different client via --UseClient (Warning: safecurl just executes curl and your details may be seen from the process list).
+
+*  Enable logging
+
+You can get additional logging with the switches "-t -l".  Please post logs if you are unable to resolve your issue with these switches ensuring to remove your username/password/cookies.
+
+
 ### Additional Trackers
-Jackett's framework allows me (and any other volunteering dev) to implement just about any new tracker in 15-60 minutes. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact me on IRC (see below).
+Jackett's framework allows our team (and any other volunteering dev) to implement new trackers in an hour or two. If you'd like support for a new tracker then feel free to leave a request on the [issues page](https://github.com/zone117x/Jackett/issues) or contact us on IRC (see below).
 
 ### Contact & Support
-I can be contact on IRC at [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett) & [irc.freenode.net#sonarr](http://webchat.freenode.net/?channels=#sonarr)
+Use the github issues pages or talk to us directly at: [irc.freenode.net#jackett](http://webchat.freenode.net/?channels=#jackett).
 
 ### Screenshots
 
-![screenshot](http://i.imgur.com/t1sVva6.png "screenshot")
+![screenshot](http://i.imgur.com/t1sVva6.png "screenshot")
\ No newline at end of file
diff --git a/src/CurlSharp/CurlEasy.cs b/src/CurlSharp/CurlEasy.cs
index 6c462834c6c9febee48ff8339228568d339e2449..5519965949de7159b190e266ce307189daf4e686 100644
--- a/src/CurlSharp/CurlEasy.cs
+++ b/src/CurlSharp/CurlEasy.cs
@@ -118,7 +118,6 @@ namespace CurlSharp
         private NativeMethods._CurlDebugCallback _pcbDebug;
         private NativeMethods._CurlIoctlCallback _pcbIoctl;
         private NativeMethods._CurlProgressCallback _pcbProgress;
-        private NativeMethods._CurlSslCtxCallback _pcbSslCtx;
 #endif
         private CurlDebugCallback _pfCurlDebug;
         private CurlHeaderCallback _pfCurlHeader;
@@ -293,18 +292,6 @@ namespace CurlSharp
             return setCurlOpt(_curlDebugData, CurlOption.DebugData);
         }
 
-        private IntPtr _curlSslCtxData = IntPtr.Zero;
-
-        /// <summary>
-        ///     Object to pass to OnSslCtxCallback.
-        /// </summary>
-        /// <param name="data"></param>
-        /// <returns></returns>
-        private CurlCode setSslCtxData(object data)
-        {
-            _curlSslCtxData = getHandle(data);
-            return setCurlOpt(_curlSslCtxData, CurlOption.SslCtxData);
-        }
 
         private IntPtr _curlIoctlData = IntPtr.Zero;
 
@@ -368,17 +355,6 @@ namespace CurlSharp
             }
         }
 
-        public object SslCtxData
-        {
-            get { return _sslContextData; }
-            set
-            {
-                _sslContextData = value;
-#if !USE_LIBCURLSHIM
-                setSslCtxData(value);
-#endif
-            }
-        }
 
         public object IoctlData
         {
@@ -538,11 +514,6 @@ namespace CurlSharp
             set { setFunctionOptions(CurlOption.IoctlFunction, value); }
         }
 
-        public CurlSslContextCallback SslContextFunction
-        {
-            get { return _pfCurlSslContext; }
-            set { setFunctionOptions(CurlOption.SslCtxFunction, value); }
-        }
 
         public string LastErrorDescription
         {
@@ -1262,7 +1233,6 @@ namespace CurlSharp
                     freeHandle(ref _curlProgressData);
                     freeHandle(ref _curlHeaderData);
                     freeHandle(ref _curlIoctlData);
-                    freeHandle(ref _curlSslCtxData);
 #endif
                     NativeMethods.curl_easy_cleanup(_pCurl);
 
@@ -1439,9 +1409,6 @@ namespace CurlSharp
                 case CurlOption.HeaderData:
                     _headerData = parameter;
                     break;
-                case CurlOption.SslCtxData:
-                    _sslContextData = parameter;
-                    break;
                 case CurlOption.IoctlData:
                     _ioctlData = parameter;
                     break;
@@ -1593,14 +1560,6 @@ namespace CurlSharp
                     break;
                 }
 
-                case CurlOption.SslCtxFunction:
-                {
-                    var sf = pfn as CurlSslContextCallback;
-                    if (sf == null)
-                        return CurlCode.BadFunctionArgument;
-                    _pfCurlSslContext = sf;
-                    break;
-                }
 
                 case CurlOption.IoctlFunction:
                 {
@@ -1949,7 +1908,6 @@ namespace CurlSharp
             _pcbProgress = _curlProgressCallback;
             _pcbDebug = _curlDebugCallback;
             _pcbHeader = _curlHeaderCallback;
-            _pcbSslCtx = _curlSslCtxCallback;
             _pcbIoctl = _curlIoctlCallback;
 
             setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.WriteFunction, _pcbWrite),
@@ -1962,8 +1920,6 @@ namespace CurlSharp
                          CurlOption.HeaderFunction);
             setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.DebugFunction, _pcbDebug),
                          CurlOption.DebugFunction);
-            setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.SslCtxFunction, _pcbSslCtx),
-                         CurlOption.SslCtxFunction);
             setLastError(NativeMethods.curl_easy_setopt_cb(_pCurl, CurlOption.IoctlFunction, _pcbIoctl),
                          CurlOption.IoctlFunction);
             setLastError(NativeMethods.curl_easy_setopt(_pCurl, CurlOption.NoProgress, (IntPtr) 0),
@@ -1974,7 +1930,6 @@ namespace CurlSharp
             setHeaderData(null);
             setProgressData(null);
             setDebugData(null);
-            setSslCtxData(null);
             setIoctlData(null);
 #endif
         }
diff --git a/src/CurlSharp/CurlSharp.csproj b/src/CurlSharp/CurlSharp.csproj
index 849dc60ab34cb8f614fefe9408e7ea30b359ad2f..de7a139ac4d131c3e8dc09602baf044d1ab94d99 100644
--- a/src/CurlSharp/CurlSharp.csproj
+++ b/src/CurlSharp/CurlSharp.csproj
@@ -10,6 +10,8 @@
     <RootNamespace>CurlSharp</RootNamespace>
     <AssemblyName>CurlSharp</AssemblyName>
     <FileAlignment>512</FileAlignment>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -20,6 +22,7 @@
     <WarningLevel>4</WarningLevel>
     <ConsolePause>false</ConsolePause>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>full</DebugType>
@@ -29,6 +32,7 @@
     <ConsolePause>false</ConsolePause>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DefineConstants>LINUX</DefineConstants>
+    <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
@@ -74,6 +78,7 @@
     <Compile Include="Enums\CurlVersionFeatureBitmask.cs" />
     <Compile Include="Callbacks\CurlEasyCallbacks.cs" />
     <Compile Include="Callbacks\CurlShareCallbacks.cs" />
+    <Compile Include="SSLFix.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
diff --git a/src/CurlSharp/Enums/CurlOption.cs b/src/CurlSharp/Enums/CurlOption.cs
index 78a7fe3e321442ba18ed99be4b4048da9ae32cb2..2eecac8f6b3cce040edaae50455cd3a200ec52fb 100644
--- a/src/CurlSharp/Enums/CurlOption.cs
+++ b/src/CurlSharp/Enums/CurlOption.cs
@@ -1075,32 +1075,6 @@ namespace CurlSharp
         /// </summary>
         SslCipherList = 10083,
 
-        /// <summary>
-        ///     Object reference to pass to the ssl context delegate set by the option
-        ///     <c>SslCtxFunction</c>, this is the pointer you'll get as the
-        ///     second parameter, otherwise <c>null</c>. (Added in 7.11.0)
-        /// </summary>
-        SslCtxData = 10109,
-
-        /// <summary>
-        ///     Reference to an <see cref="CurlEasy.CurlSslContextCallback" /> delegate.
-        ///     This delegate gets called by libcurl just before the initialization of
-        ///     an Ssl connection after having processed all other Ssl related options
-        ///     to give a last chance to an application to modify the behaviour of
-        ///     openssl's ssl initialization. The <see cref="CurlSslContext" /> parameter
-        ///     wraps a pointer to an openssl SSL_CTX. If an error is returned no attempt
-        ///     to establish a connection is made and the perform operation will return
-        ///     the error code from this callback function. Set the parm argument with
-        ///     the <c>SslCtxData</c> option. This option was introduced
-        ///     in 7.11.0.
-        ///     <note>
-        ///         To use this properly, a non-trivial amount of knowledge of the openssl
-        ///         libraries is necessary. Using this function allows for example to use
-        ///         openssl callbacks to add additional validation code for certificates,
-        ///         and even to change the actual URI of an HTTPS request.
-        ///     </note>
-        /// </summary>
-        SslCtxFunction = 20108,
 
         /// <summary>
         ///     Pass an <c>int</c>. Set if we should verify the common name from the
diff --git a/src/CurlSharp/SSLFix.cs b/src/CurlSharp/SSLFix.cs
new file mode 100644
index 0000000000000000000000000000000000000000..940767f936bf2c54e1ee2aa7a526ea4deb92ccef
--- /dev/null
+++ b/src/CurlSharp/SSLFix.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CurlSharp
+{
+    public class SSLFix
+    {
+        public const string CipherList = "rsa_aes_128_sha,ecdhe_rsa_aes_256_sha,ecdhe_ecdsa_aes_128_sha";
+    }
+}
diff --git a/src/Jackett.Console/App.config b/src/Jackett.Console/App.config
index da396d09005aed9b2a2466bd7d9bf0f17280c0a3..519060c99788e923d52c3703662eda2790a429dc 100644
--- a/src/Jackett.Console/App.config
+++ b/src/Jackett.Console/App.config
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
     </startup>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
@@ -31,4 +31,4 @@
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/src/Jackett.Console/ConsoleOptions.cs b/src/Jackett.Console/ConsoleOptions.cs
index cf05c933c97bc134e63ccc8d91e5099543eb5eaa..cefa8d94fedd177a804bedd7474534ce4b3070da 100644
--- a/src/Jackett.Console/ConsoleOptions.cs
+++ b/src/Jackett.Console/ConsoleOptions.cs
@@ -24,8 +24,8 @@ namespace Jackett.Console
         [Option('t', "Tracing", HelpText = "Enable tracing")]
         public bool Tracing { get; set; }
 
-        [Option('c', "UseCurlExec",  HelpText = "Execute curl rather than libcurl for all outgoing requests.")]
-        public bool UseCurlExec { get; set; }
+        [Option('c', "UseClient",  HelpText = "Override web client selection. [automatic(Default)/libcurl/safecurl/httpclient]")]
+        public string Client { get; set; }
 
         [Option('s', "Start",  HelpText = "Start the Jacket Windows service (Must be admin)")]
         public bool StartService { get; set; }
@@ -33,7 +33,7 @@ namespace Jackett.Console
         [Option('k', "Stop", HelpText = "Stop the Jacket Windows service (Must be admin)")]
         public bool StopService { get; set; }
 
-        [Option('x', "ListenPublic", HelpText = "Listen publicly")]
+        [Option('x', "ListenPublic", HelpText = "Listen publicly [true/false]")]
         public bool? ListenPublic { get; set; }
 
         [Option('h', "Help",  HelpText = "Show Help")]
@@ -47,5 +47,8 @@ namespace Jackett.Console
 
         [Option('m', "MigrateSettings", HelpText = "Migrate settings manually (Must be admin on Windows)")]
         public bool MigrateSettings { get; set; }
+
+        [Option('f', "SSLFix", HelpText = "Linux Libcurl NSS Missing ECC Ciphers workaround (Use if you can't access some trackers) [true/false].")]
+        public bool? SSLFix { get; set; }
     }
 }
diff --git a/src/Jackett.Console/Jackett.Console.csproj b/src/Jackett.Console/Jackett.Console.csproj
index 022bbaee6b39423547346cdbd5637d0824cc07c3..11b25b22394dbc96175859f0e7e15f58e89f0c5c 100644
--- a/src/Jackett.Console/Jackett.Console.csproj
+++ b/src/Jackett.Console/Jackett.Console.csproj
@@ -9,11 +9,12 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Jackett.Console</RootNamespace>
     <AssemblyName>JackettConsole</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -45,22 +46,33 @@
       <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.Owin">
+    <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.WebApi.Owin">
+    <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
+      <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
+      <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.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>
     </Reference>
-    <Reference Include="Microsoft.AspNet.Identity.Core">
+    <Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
@@ -74,8 +86,9 @@
       <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Microsoft.Owin.Host.SystemWeb">
+    <Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
@@ -90,8 +103,8 @@
       <Private>True</Private>
     </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
       <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
@@ -116,12 +129,13 @@
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="System.Web.Http.Owin">
+    <Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="System.Web.Http.Tracing">
+    <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
@@ -137,9 +151,7 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="App.config" />
-    <None Include="packages.config">
-      <SubType>Designer</SubType>
-    </None>
+    <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\CurlSharp\CurlSharp.csproj">
@@ -155,6 +167,7 @@
     <Content Include="jackett.ico" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
   <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
     <PropertyGroup>
diff --git a/src/Jackett.Console/Program.cs b/src/Jackett.Console/Program.cs
index afbbd5ce1066483f895d8d367569138342facd9f..90c49c6b584415e9f5b3bfabd4758b1749538d51 100644
--- a/src/Jackett.Console/Program.cs
+++ b/src/Jackett.Console/Program.cs
@@ -9,6 +9,7 @@ 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;
@@ -36,9 +37,12 @@ namespace JackettConsole
                 {
                     /*  ======     Options    =====  */
 
+                    // SSL Fix
+                    Startup.DoSSLFix = options.SSLFix;
+
                     // Use curl
-                    if (options.UseCurlExec)
-                        Startup.CurlSafe = true;
+                    if (options.Client!=null)
+                        Startup.ClientOverride = options.Client.ToLowerInvariant();
 
                     // Logging
                     if (options.Logging)
@@ -50,15 +54,16 @@ namespace JackettConsole
 
                     // Log after the fact as using the logger will cause the options above to be used
 
-                    if (options.UseCurlExec)
-                        Engine.Logger.Info("Safe curl enabled.");
-
                     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.");
                     /*  ======     Actions    =====  */
 
                     // Install service
@@ -173,7 +178,6 @@ namespace JackettConsole
 
                 Engine.Server.Initalize();
                 Engine.Server.Start();
-                Engine.Logger.Info("Running in console mode!");
                 Engine.RunTime.Spin();
                 Engine.Logger.Info("Server thread exit");
             }
diff --git a/src/Jackett.Console/packages.config b/src/Jackett.Console/packages.config
index b883a0e513c5642dbe56cda04a24eec6b0a5a469..baad01c07506632361077465f2ca6dea80f9cca1 100644
--- a/src/Jackett.Console/packages.config
+++ b/src/Jackett.Console/packages.config
@@ -1,26 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Autofac" version="3.5.2" targetFramework="net452" />
-  <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" />
-  <package id="CommandLineParser" version="1.9.71" targetFramework="net452" />
-  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
-  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" />
-  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" />
-  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" />
-  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
-  <package id="NLog" version="4.0.1" targetFramework="net452" />
-  <package id="Owin" version="1.0" targetFramework="net452" />
+  <package id="Autofac" version="3.5.2" targetFramework="net45" />
+  <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" />
+  <package id="AutoMapper" version="3.3.1" targetFramework="net45" />
+  <package id="CommandLineParser" version="1.9.71" targetFramework="net45" />
+  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+  <package id="NLog" version="4.0.1" targetFramework="net45" />
+  <package id="Owin" version="1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/Jackett.Distribution/App.config b/src/Jackett.Distribution/App.config
index 88fa4027bda397de6bf19f0940e5dd6026c877f9..d1428ad712d72a50520dab5091ddcb5ea2b5f3e6 100644
--- a/src/Jackett.Distribution/App.config
+++ b/src/Jackett.Distribution/App.config
@@ -1,6 +1,6 @@
-<?xml version="1.0" encoding="utf-8" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
     </startup>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/src/Jackett.Distribution/Jackett.Distribution.csproj b/src/Jackett.Distribution/Jackett.Distribution.csproj
index 6cf40976a4a2910121d9e89ed656e9f5249cb379..da0fa2e12d6c099cdda67261762c6e4600859e8d 100644
--- a/src/Jackett.Distribution/Jackett.Distribution.csproj
+++ b/src/Jackett.Distribution/Jackett.Distribution.csproj
@@ -9,9 +9,10 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Jackett.Distribution</RootNamespace>
     <AssemblyName>JackettDistribution</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
diff --git a/src/Jackett.Distribution/packages.config b/src/Jackett.Distribution/packages.config
index 869635dc0165071ae92ba9eef454e71755e170f6..afa3e1ac90ff8fae543a16b74efa2a48879b13a6 100644
--- a/src/Jackett.Distribution/packages.config
+++ b/src/Jackett.Distribution/packages.config
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Octokit" version="0.14.0" targetFramework="net452" />
+  <package id="Octokit" version="0.14.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/Jackett.Service/App.config b/src/Jackett.Service/App.config
index e74e545f8aa971185e0a54404e426885bc5abe74..ac8dbeb66b3fd7fe26fd03e8b3f320aedbeea163 100644
--- a/src/Jackett.Service/App.config
+++ b/src/Jackett.Service/App.config
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
     </startup>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
@@ -27,4 +27,4 @@
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/src/Jackett.Service/Jackett.Service.csproj b/src/Jackett.Service/Jackett.Service.csproj
index ff213d5cd14d3c8b6c7a4566fd3abb29ce0d11bc..214be78777353dae79ef63efec087125ed30da9d 100644
--- a/src/Jackett.Service/Jackett.Service.csproj
+++ b/src/Jackett.Service/Jackett.Service.csproj
@@ -9,11 +9,12 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Jackett.Service</RootNamespace>
     <AssemblyName>JackettService</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -42,46 +43,49 @@
       <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.Owin">
+    <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.WebApi.Owin">
+    <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin.FileSystems, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.Owin.FileSystems.3.0.1\lib\net45\Microsoft.Owin.FileSystems.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="Microsoft.Owin.Host.HttpListener">
+    <Reference Include="Microsoft.Owin.Host.HttpListener, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin.StaticFiles, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
@@ -90,8 +94,8 @@
       <Private>True</Private>
     </Reference>
     <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
@@ -99,15 +103,16 @@
     </Reference>
     <Reference Include="System.Net.Http.WebRequest" />
     <Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Web.Http.Owin, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="System.Web.Http.Tracing">
+    <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
diff --git a/src/Jackett.Service/packages.config b/src/Jackett.Service/packages.config
index c3b90ef0907c7394c97ec4050b140dcc6ed56a82..eb26439ff27bc14fa5904ed765b79c92f510d0c1 100644
--- a/src/Jackett.Service/packages.config
+++ b/src/Jackett.Service/packages.config
@@ -1,23 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Autofac" version="3.5.2" targetFramework="net452" />
-  <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
-  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" />
-  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" />
-  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" />
-  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
-  <package id="NLog" version="4.0.1" targetFramework="net452" />
-  <package id="Owin" version="1.0" targetFramework="net452" />
+  <package id="Autofac" version="3.5.2" targetFramework="net45" />
+  <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+  <package id="NLog" version="4.0.1" targetFramework="net45" />
+  <package id="Owin" version="1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/Jackett.Test/Indexers/BakaBTTests.cs b/src/Jackett.Test/Indexers/BakaBTTests.cs
index 0b9b471eb73a403d3ac361d73187f90b8649083e..660ae9328b9c5bbdb701895b04ee3d51f987f9c6 100644
--- a/src/Jackett.Test/Indexers/BakaBTTests.cs
+++ b/src/Jackett.Test/Indexers/BakaBTTests.cs
@@ -156,22 +156,22 @@ namespace JackettTest.Indexers
             var indexer = TestUtil.Container.ResolveNamed<IIndexer>(BakaBT.GetIndexerID(typeof(BakaBT))) as BakaBT;
 
             indexer.LoadFromSavedConfiguration(JObject.Parse("{\"cookies\":\"bbtid=c\"}"));
-            var results = await indexer.PerformQuery(new Jackett.Models.TorznabQuery() { SanitizedSearchTerm = "Series S1", Season = 1 });
-
-            results.Length.Should().Be(44);
-            results[0].Title.Should().Be("Golden Time Season 1 (BD 720p) [FFF]");
-            results[0].Guid.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
-            results[0].Comments.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
-            results[0].Size.Should().Be(10307921920);
-            results[0].Description.Should().Be("Golden Time Season 1 (BD 720p) [FFF]");
-            results[0].Link.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
-            results[0].Peers.Should().Be(161);
-            results[0].Seeders.Should().Be(151);
-            results[0].MinimumRatio.Should().Be(1);
-
-            results[1].Title.Should().Be("Yowamushi Pedal Season 1 (BD 720p) [Commie]");
-            results[4].Title.Should().Be("Dungeon ni Deai o Motomeru no wa Machigatte Iru Darouka: Familia Myth Season 1 (480p) [HorribleSubs]");
-            results[5].Title.Should().Be("Is It Wrong to Try to Pick Up Girls in a Dungeon? Season 1 (480p) [HorribleSubs]");
+            var results = await indexer.PerformQuery(new Jackett.Models.TorznabQuery() { SearchTerm = "Series S1", Season = 1 });
+
+            results.Count().Should().Be(44);
+            results.First().Title.Should().Be("Golden Time Season 1 (BD 720p) [FFF]");
+            results.First().Guid.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
+            results.First().Comments.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
+            results.First().Size.Should().Be(10307921920);
+            results.First().Description.Should().Be("Golden Time Season 1 (BD 720p) [FFF]");
+            results.First().Link.Should().Be("http://bakabt.me/torrent/180302/golden-time-bd-720p-fff");
+            results.First().Peers.Should().Be(161);
+            results.First().Seeders.Should().Be(151);
+            results.First().MinimumRatio.Should().Be(1);
+
+            results.ElementAt(1).Title.Should().Be("Yowamushi Pedal Season 1 (BD 720p) [Commie]");
+            results.ElementAt(4).Title.Should().Be("Dungeon ni Deai o Motomeru no wa Machigatte Iru Darouka: Familia Myth Season 1 (480p) [HorribleSubs]");
+            results.ElementAt(5).Title.Should().Be("Is It Wrong to Try to Pick Up Girls in a Dungeon? Season 1 (480p) [HorribleSubs]");
         }
     }
 }
diff --git a/src/Jackett.Test/Jackett.Test.csproj b/src/Jackett.Test/Jackett.Test.csproj
index 8d0bbc459c94e3693a26dda90d1ebd105008c5d0..234f876d19d19caed14485c9d1f557f1c5b40b1c 100644
--- a/src/Jackett.Test/Jackett.Test.csproj
+++ b/src/Jackett.Test/Jackett.Test.csproj
@@ -8,7 +8,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>JackettTest</RootNamespace>
     <AssemblyName>JackettTest</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
     <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@@ -18,6 +18,7 @@
     <TestProjectType>UnitTest</TestProjectType>
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -158,6 +159,7 @@
     <Compile Include="TestIIndexerManagerServiceHelper.cs" />
     <Compile Include="TestUtil.cs" />
     <Compile Include="TestWebClient.cs" />
+    <Compile Include="Util\ServerUtilTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
diff --git a/src/Jackett.Test/TestWebClient.cs b/src/Jackett.Test/TestWebClient.cs
index dd375c38c855be0b88f5dbd094a798e934d7bded..4104972ed35d0436186c5043a9f1fffaa5fc32b1 100644
--- a/src/Jackett.Test/TestWebClient.cs
+++ b/src/Jackett.Test/TestWebClient.cs
@@ -31,5 +31,10 @@ namespace JackettTest
         {
             return Task.FromResult<WebClientStringResult>(stringCallbacks.Where(r => r.Key.Equals(request)).First().Value.Invoke(request));
         }
+
+        public void Init()
+        {
+          
+        }
     }
 }
diff --git a/src/Jackett.Test/Util/ServerUtilTests.cs b/src/Jackett.Test/Util/ServerUtilTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c59b942e59350fcef694e41e38d77339dd1dc73b
--- /dev/null
+++ b/src/Jackett.Test/Util/ServerUtilTests.cs
@@ -0,0 +1,50 @@
+using Jackett.Utils.Clients;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Autofac;
+using Jackett.Indexers;
+using FluentAssertions;
+using Newtonsoft.Json.Linq;
+using Jackett;
+using Newtonsoft.Json;
+using Jackett.Utils;
+
+namespace JackettTest.Indexers
+{
+    [TestFixture]
+    class ServerUtilTests : TestBase
+    {
+        [Test]
+        public void ResureRedirectIsFullyQualified_makes_redicts_fully_qualified()
+        {
+            var res = new WebClientByteResult()
+            {
+                RedirectingTo = "list?p=1"
+            };
+
+            var req = new WebRequest()
+            {
+                Url = "http://my.domain.com/page.php"
+            };
+
+            // Not fully qualified  requiring redirect
+            ServerUtil.ResureRedirectIsFullyQualified(req, res);
+            Assert.AreEqual(res.RedirectingTo, "http://my.domain.com/list?p=1");
+
+            // Fully qualified not needing modified
+            res.RedirectingTo = "http://a.domain/page.htm";
+            ServerUtil.ResureRedirectIsFullyQualified(req, res);
+            Assert.AreEqual(res.RedirectingTo, "http://a.domain/page.htm");
+
+            // Relative  requiring redirect
+            req.Url = "http://my.domain.com/dir/page.php";
+            res.RedirectingTo = "a/dir/page.html";
+            ServerUtil.ResureRedirectIsFullyQualified(req, res);
+            Assert.AreEqual(res.RedirectingTo, "http://my.domain.com/dir/a/dir/page.html");
+        }
+    }
+}
diff --git a/src/Jackett.Test/app.config b/src/Jackett.Test/app.config
index c0ff2bc0ee0c2305995993b67249bed92da43f25..eda8c8545ee4aead9709d3c7ea5285a5762afd7d 100644
--- a/src/Jackett.Test/app.config
+++ b/src/Jackett.Test/app.config
@@ -24,4 +24,4 @@
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
-</configuration>
\ No newline at end of file
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /></startup></configuration>
diff --git a/src/Jackett.Test/packages.config b/src/Jackett.Test/packages.config
index 8ac788eefbe16855f82b422b56e8da78c593a3f5..3813a8d524c979069f10fb74c974c2e9c21f16d4 100644
--- a/src/Jackett.Test/packages.config
+++ b/src/Jackett.Test/packages.config
@@ -1,25 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Autofac" version="3.5.2" targetFramework="net452" />
-  <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" />
-  <package id="CsQuery" version="1.3.4" targetFramework="net452" />
-  <package id="FluentAssertions" version="3.4.1" targetFramework="net452" />
-  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
-  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" />
-  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" />
-  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" />
-  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
-  <package id="NLog" version="4.0.1" targetFramework="net452" />
-  <package id="NUnit" version="2.6.4" targetFramework="net452" />
-  <package id="NUnitTestAdapter" version="2.0.0" targetFramework="net452" />
-  <package id="Owin" version="1.0" targetFramework="net452" />
+  <package id="Autofac" version="3.5.2" targetFramework="net45" />
+  <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" />
+  <package id="CsQuery" version="1.3.4" targetFramework="net45" />
+  <package id="FluentAssertions" version="3.4.1" targetFramework="net45" />
+  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+  <package id="NLog" version="4.0.1" targetFramework="net45" />
+  <package id="NUnit" version="2.6.4" targetFramework="net45" />
+  <package id="NUnitTestAdapter" version="2.0.0" targetFramework="net45" />
+  <package id="Owin" version="1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/Jackett.Tray/App.config b/src/Jackett.Tray/App.config
index e74e545f8aa971185e0a54404e426885bc5abe74..ac8dbeb66b3fd7fe26fd03e8b3f320aedbeea163 100644
--- a/src/Jackett.Tray/App.config
+++ b/src/Jackett.Tray/App.config
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
     </startup>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
@@ -27,4 +27,4 @@
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/src/Jackett.Tray/Jackett.Tray.csproj b/src/Jackett.Tray/Jackett.Tray.csproj
index a7801f0dcff5dbdb8579b3a8ebcfd9419eac6189..ce52770116bddd32ad71b6fd9c73b120763e73e9 100644
--- a/src/Jackett.Tray/Jackett.Tray.csproj
+++ b/src/Jackett.Tray/Jackett.Tray.csproj
@@ -9,11 +9,12 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Jackett.Tray</RootNamespace>
     <AssemblyName>JackettTray</AssemblyName>
-    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -54,6 +55,14 @@
       <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
+      <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="AutoMapper.Net4, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
+      <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
       <Private>True</Private>
@@ -143,8 +152,11 @@
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>
       <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
     </Compile>
-    <None Include="packages.config" />
+    <None Include="packages.config">
+      <SubType>Designer</SubType>
+    </None>
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
       <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@@ -156,7 +168,9 @@
     </Compile>
   </ItemGroup>
   <ItemGroup>
-    <None Include="App.config" />
+    <None Include="App.config">
+      <SubType>Designer</SubType>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <Content Include="jackett.ico" />
@@ -186,6 +200,7 @@
     </PropertyGroup>
     <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
   </Target>
+  <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">
diff --git a/src/Jackett.Tray/Main.Designer.cs b/src/Jackett.Tray/Main.Designer.cs
index ae47ee2bdc71ee024b90076ce28a6b7569ca083a..1c3a770d1c22bd846e894bdfeb421c9d06e1c117 100644
--- a/src/Jackett.Tray/Main.Designer.cs
+++ b/src/Jackett.Tray/Main.Designer.cs
@@ -36,9 +36,9 @@
             this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
             this.backgroundMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.serviceControlMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
             this.toolStripMenuItemAutoStart = new System.Windows.Forms.ToolStripMenuItem();
             this.toolStripMenuItemShutdown = new System.Windows.Forms.ToolStripMenuItem();
-            this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
             this.contextMenuStrip1.SuspendLayout();
             this.SuspendLayout();
             // 
@@ -88,12 +88,18 @@
             this.serviceControlMenuItem.Text = "Start Service";
             this.serviceControlMenuItem.Click += new System.EventHandler(this.serviceControlMenuItem_Click);
             // 
+            // toolStripSeparator2
+            // 
+            this.toolStripSeparator2.Name = "toolStripSeparator2";
+            this.toolStripSeparator2.Size = new System.Drawing.Size(288, 6);
+            // 
             // toolStripMenuItemAutoStart
             // 
             this.toolStripMenuItemAutoStart.CheckOnClick = true;
             this.toolStripMenuItemAutoStart.Name = "toolStripMenuItemAutoStart";
             this.toolStripMenuItemAutoStart.Size = new System.Drawing.Size(291, 22);
             this.toolStripMenuItemAutoStart.Text = "Auto-start on boot";
+            this.toolStripMenuItemAutoStart.Visible = false;
             // 
             // toolStripMenuItemShutdown
             // 
@@ -101,11 +107,6 @@
             this.toolStripMenuItemShutdown.Size = new System.Drawing.Size(291, 22);
             this.toolStripMenuItemShutdown.Text = "Shutdown";
             // 
-            // toolStripSeparator2
-            // 
-            this.toolStripSeparator2.Name = "toolStripSeparator2";
-            this.toolStripSeparator2.Size = new System.Drawing.Size(288, 6);
-            // 
             // Main
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
diff --git a/src/Jackett.Tray/Main.cs b/src/Jackett.Tray/Main.cs
index fee4ca124f8480a054cccb117da06ae35e46a00f..7a02445e8f4b246a76cf0ed0e009324c3b0fab00 100644
--- a/src/Jackett.Tray/Main.cs
+++ b/src/Jackett.Tray/Main.cs
@@ -28,6 +28,11 @@ namespace JackettTray
 
             toolStripMenuItemWebUI.Click += toolStripMenuItemWebUI_Click;
             toolStripMenuItemShutdown.Click += toolStripMenuItemShutdown_Click;
+#if __MonoCS__
+            // No shortcuts on linux
+#else
+            toolStripMenuItemAutoStart.Visible = true;
+#endif
 
             Engine.Server.Initalize();
 
@@ -91,13 +96,16 @@ namespace JackettTray
 
         private void CreateShortcut()
         {
-
+#if __MonoCS__
+            // No shortcuts on linux
+#else
             var appPath = Assembly.GetExecutingAssembly().Location;
             var shell = new IWshRuntimeLibrary.WshShell();
             var shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(ShortcutPath);
             shortcut.Description = Assembly.GetExecutingAssembly().GetName().Name;
             shortcut.TargetPath = appPath;
             shortcut.Save();
+#endif
         }
 
         private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
diff --git a/src/Jackett.Tray/Properties/Resources.Designer.cs b/src/Jackett.Tray/Properties/Resources.Designer.cs
index b6ce33f0b003cff2f785569faf90dbcea67554ce..a4cb133198c5f85e4bee3f835fc14d58c5c7894f 100644
--- a/src/Jackett.Tray/Properties/Resources.Designer.cs
+++ b/src/Jackett.Tray/Properties/Resources.Designer.cs
@@ -8,10 +8,10 @@
 // </auto-generated>
 //------------------------------------------------------------------------------
 
-namespace Jackett.Tray.Properties
-{
-
-
+namespace Jackett.Tray.Properties {
+    using System;
+    
+    
     /// <summary>
     ///   A strongly-typed resource class, for looking up localized strings, etc.
     /// </summary>
@@ -22,48 +22,40 @@ namespace Jackett.Tray.Properties
     [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
-    internal class Resources
-    {
-
+    internal class Resources {
+        
         private static global::System.Resources.ResourceManager resourceMan;
-
+        
         private static global::System.Globalization.CultureInfo resourceCulture;
-
+        
         [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
-        internal Resources()
-        {
+        internal Resources() {
         }
-
+        
         /// <summary>
         ///   Returns the cached ResourceManager instance used by this class.
         /// </summary>
         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
-        internal static global::System.Resources.ResourceManager ResourceManager
-        {
-            get
-            {
-                if ((resourceMan == null))
-                {
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Jackett.Tray.Properties.Resources", typeof(Resources).Assembly);
                     resourceMan = temp;
                 }
                 return resourceMan;
             }
         }
-
+        
         /// <summary>
         ///   Overrides the current thread's CurrentUICulture property for all
         ///   resource lookups using this strongly typed resource class.
         /// </summary>
         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
-        internal static global::System.Globalization.CultureInfo Culture
-        {
-            get
-            {
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
                 return resourceCulture;
             }
-            set
-            {
+            set {
                 resourceCulture = value;
             }
         }
diff --git a/src/Jackett.Tray/Properties/Settings.Designer.cs b/src/Jackett.Tray/Properties/Settings.Designer.cs
index c110e9e5ed4c5c6f19ed36a66a568d76c3e86e64..f4fbc6273e68fce880112ff542fdf9d6b60e1f30 100644
--- a/src/Jackett.Tray/Properties/Settings.Designer.cs
+++ b/src/Jackett.Tray/Properties/Settings.Designer.cs
@@ -8,21 +8,17 @@
 // </auto-generated>
 //------------------------------------------------------------------------------
 
-namespace Jackett.Tray.Properties
-{
-
-
+namespace Jackett.Tray.Properties {
+    
+    
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
-    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
-    {
-
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
         private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
-        public static Settings Default
-        {
-            get
-            {
+        
+        public static Settings Default {
+            get {
                 return defaultInstance;
             }
         }
diff --git a/src/Jackett.Tray/packages.config b/src/Jackett.Tray/packages.config
index c3b90ef0907c7394c97ec4050b140dcc6ed56a82..13667dfd31b06c6063af94b9b275fd6653966bea 100644
--- a/src/Jackett.Tray/packages.config
+++ b/src/Jackett.Tray/packages.config
@@ -1,23 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Autofac" version="3.5.2" targetFramework="net452" />
-  <package id="Autofac.Owin" version="3.1.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net452" />
-  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net452" />
-  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net452" />
-  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net452" />
-  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net452" />
-  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net452" />
-  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net452" />
-  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
-  <package id="NLog" version="4.0.1" targetFramework="net452" />
-  <package id="Owin" version="1.0" targetFramework="net452" />
+  <package id="Autofac" version="3.5.2" targetFramework="net45" />
+  <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" />
+  <package id="AutoMapper" version="3.3.1" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+  <package id="NLog" version="4.0.1" targetFramework="net45" />
+  <package id="Owin" version="1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/src/Jackett/App.config b/src/Jackett/App.config
index 547fa70d5a3e2401a33267f1952c9285486bb6ea..5415cbdd9a3baaae6df6fc38ff493426fd16703c 100644
--- a/src/Jackett/App.config
+++ b/src/Jackett/App.config
@@ -2,7 +2,7 @@
 <configuration>
   
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
     </startup>
 
   <runtime>
diff --git a/src/Jackett/Content/custom.css b/src/Jackett/Content/custom.css
index 1ac9fb8f7456bea9188d1be6a59d349d1ede1bec..e4b2279a9d9bece81b688c34de9d9b741e996000 100644
--- a/src/Jackett/Content/custom.css
+++ b/src/Jackett/Content/custom.css
@@ -49,7 +49,7 @@
 }
 
 .indexer {
-    height: 180px;
+    height: 235px;
 }
 
 .add-indexer {
@@ -114,6 +114,10 @@
     height: 20px;
 }
 
+[data-type=hiddendata]{
+    display: none;
+}
+
 .spinner {
     -webkit-animation: spin 2s infinite linear;
     -moz-animation: spin 2s infinite linear;
@@ -210,4 +214,13 @@ hr {
 
 .modal-fillwidth {
     width: 1200px;
+}
+
+.indexer-caps {
+   padding: 0px 15px 15px 15px;
+   border-top: 1px solid #e5e5e5;
+}
+
+.indexer-caps table {
+    border-bottom:   1px solid #ddd;
 }
\ No newline at end of file
diff --git a/src/Jackett/Content/custom.js b/src/Jackett/Content/custom.js
index 9cb3c434993d4893dce0745fdf4f6c7ddacc35fd..5aca89c2bf15d79adfb5dca1a9b53272c11d2eb3 100644
--- a/src/Jackett/Content/custom.js
+++ b/src/Jackett/Content/custom.js
@@ -136,7 +136,8 @@ function displayIndexers(items) {
     var unconfiguredIndexerTemplate = Handlebars.compile($("#templates > .unconfigured-indexer")[0].outerHTML);
     for (var i = 0; i < items.length; i++) {
         var item = items[i];
-        item.torznab_host = resolveUrl("/api/" + item.id);
+        item.torznab_host = resolveUrl("/torznab/" + item.id);
+        item.potato_host = resolveUrl("/potato/" + item.id);  
         if (item.configured)
             $('#indexers').append(indexerTemplate(item));
         else
@@ -211,7 +212,7 @@ function displayIndexerSetup(id) {
             return;
         }
 
-        populateSetupForm(id, data.name, data.config);
+        populateSetupForm(id, data.name, data.config, data.caps);
 
     }).fail(function () {
         doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
@@ -236,17 +237,12 @@ function populateConfigItems(configForm, config) {
     }
 }
 
-function newConfigModal(title, config) {
-    //config-setup-modal
-    var configTemplate = Handlebars.compile($("#templates > .config-setup-modal")[0].outerHTML);
-    var configForm = $(configTemplate({ title: title }));
-
+function newConfigModal(title, config, caps) {
+    var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html());
+    var configForm = $(configTemplate({ title: title, caps: caps }));
     $("#modals").append(configForm);
-
     populateConfigItems(configForm, config);
-
     return configForm;
-    //modal.remove();
 }
 
 function getConfigModalJson(configForm) {
@@ -256,6 +252,9 @@ function getConfigModalJson(configForm) {
         var type = $el.data("type");
         var id = $el.data("id");
         switch (type) {
+            case "hiddendata":
+                configJson[id] = $el.find(".setup-item-hiddendata input").val();
+                break;
             case "inputstring":
                 configJson[id] = $el.find(".setup-item-inputstring input").val();
                 break;
@@ -267,8 +266,8 @@ function getConfigModalJson(configForm) {
     return configJson;
 }
 
-function populateSetupForm(indexerId, name, config) {
-    var configForm = newConfigModal(name, config);
+function populateSetupForm(indexerId, name, config, caps) {
+    var configForm = newConfigModal(name, config, caps);
     var $goButton = configForm.find(".setup-indexer-go");
     $goButton.click(function () {
         var data = { indexer: indexerId, name: name };
diff --git a/src/Jackett/Content/index.html b/src/Jackett/Content/index.html
index a94688fd71e721abb8207ca3577555b6e91399d2..22157c4ffb595b1c4b64c29fa7cc3e6d1e06b8b0 100644
--- a/src/Jackett/Content/index.html
+++ b/src/Jackett/Content/index.html
@@ -38,6 +38,7 @@
                                     <th>First Seen</th>
                                     <th>Tracker</th>
                                     <th>Name</th>
+                                    <th>Category</th>
                                     <th>Seeds</th>
                                     <th>Leechers</th>
                                     <th>Download</th>
@@ -52,6 +53,7 @@
                                     <td>{{formatRelative  FirstSeen}}</td>
                                     <td>{{Tracker}}</td>
                                     <td><a href="{{Comments}}">{{Title}}</a></td>
+                                    <td>{{CategoryDesc}}</td>
                                     <td>{{Seeders}}</td>
                                     <td>{{Peers}}</td>
                                     <td><a href="{{Link}}"><i class="fa fa-download"></i></a></td>
@@ -68,6 +70,45 @@
         </div>
     </script>
 
+    <script id="jackett-config-setup-modal" type="text/x-handlebars-template">
+        <div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                        <h4 class="modal-title">{{title}}</h4>
+                    </div>
+                    <div class="modal-body">
+                        <form class="config-setup-form"></form>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary setup-indexer-go">Okay</button>
+                    </div>
+                    <div class="indexer-caps">
+                        <h4>Capabilities</h4>
+                        <table class="dataTable compact cell-border hover stripe">
+                            <thead>
+                                <tr>
+                                    <th>Category</th>
+                                    <th>Description</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {{#each caps}}
+                                <tr>
+                                    <td>{{ID}}</td>
+                                    <td>{{Name}}</td>
+                                </tr>
+                                {{/each}}
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </script>
+
     <title>Jackett</title>
 </head>
 <body>
@@ -147,24 +188,6 @@
     <div id="modals"></div>
 
     <div id="templates">
-        <div class="config-setup-modal modal fade" tabindex="-1" role="dialog" aria-hidden="true">
-            <div class="modal-dialog">
-                <div class="modal-content">
-                    <div class="modal-header">
-                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
-                        <h4 class="modal-title">{{title}}</h4>
-                    </div>
-                    <div class="modal-body">
-                        <form class="config-setup-form"></form>
-                    </div>
-                    <div class="modal-footer">
-                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-                        <button type="button" class="btn btn-primary setup-indexer-go">Okay</button>
-                    </div>
-                </div>
-            </div>
-        </div>
-
         <button class="indexer card add-indexer" data-toggle="modal" data-target="#select-indexer-modal">
             <div class="indexer-add-content">
                 <span class="glyphicon glyphicon glyphicon-plus" aria-hidden="true"></span>
@@ -191,6 +214,13 @@
             <div class="indexer-host">
                 <b>Torznab Host:</b>
                 <input class="form-control" type="text" value="{{torznab_host}}" placeholder="Torznab Host" readonly="">
+                <b>CouchPotato Host:</b>
+                {{#if potatoenabled}}
+               
+                <input class="form-control" type="text" value="{{potato_host}}" placeholder="Torznab Host" readonly="">
+                {{else}}
+                <input class="form-control" type="text" value="Not availible" placeholder="Torznab Host" readonly="">
+                {{/if}}
             </div>
         </div>
 
@@ -213,7 +243,6 @@
             {{else}}
             <input class="form-control" type="text" value="{{{value}}}" />
             {{/if}}
-
         </div>
 
         <div class="setup-item-inputbool">
@@ -225,6 +254,9 @@
         </div>
         <img class="setup-item-displayimage" src="{{{value}}}" />
         <div class="setup-item-displayinfo alert alert-info" role="alert">{{{value}}}</div>
+        <div class="setup-item-hiddendata">
+            <input class="form-control" type="text" value="{{{value}}}" />
+        </div>
 
         <span class="spinner glyphicon glyphicon-refresh"></span>
 
diff --git a/src/Jackett/Content/logos/BakaBT.png b/src/Jackett/Content/logos/bakabt.png
similarity index 100%
rename from src/Jackett/Content/logos/BakaBT.png
rename to src/Jackett/Content/logos/bakabt.png
diff --git a/src/Jackett/Content/logos/immortalseed.png b/src/Jackett/Content/logos/immortalseed.png
new file mode 100644
index 0000000000000000000000000000000000000000..7e8503e115d02cd22d7eebf24994e341075a2011
Binary files /dev/null and b/src/Jackett/Content/logos/immortalseed.png differ
diff --git a/src/Jackett/Content/logos/rarbg.png b/src/Jackett/Content/logos/rarbg.png
deleted file mode 100644
index da8bebbbc75833e41f4835d2262de87c6d7b7f3e..0000000000000000000000000000000000000000
Binary files a/src/Jackett/Content/logos/rarbg.png and /dev/null differ
diff --git a/src/Jackett/Content/logos/torrentbytes.png b/src/Jackett/Content/logos/torrentbytes.png
new file mode 100644
index 0000000000000000000000000000000000000000..245c86aeac2555bdecb545223b5af6c9ce441b1b
Binary files /dev/null and b/src/Jackett/Content/logos/torrentbytes.png differ
diff --git a/src/Jackett/Controllers/AdminController.cs b/src/Jackett/Controllers/AdminController.cs
index 4900f951a692311c0a2c578140231f9f839e95f8..651a92aacc1d49ee5ac1d64b3c469e9b02e9914f 100644
--- a/src/Jackett/Controllers/AdminController.cs
+++ b/src/Jackett/Controllers/AdminController.cs
@@ -1,8 +1,11 @@
 using Autofac;
+using Jackett.Indexers;
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
+using NLog;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -32,8 +35,9 @@ namespace Jackett.Controllers
         private ISecuityService securityService;
         private IProcessService processService;
         private ICacheService cacheService;
+        private Logger logger;
 
-        public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c)
+        public AdminController(IConfigurationService config, IIndexerManagerService i, IServerService ss, ISecuityService s, IProcessService p, ICacheService c, Logger l)
         {
             this.config = config;
             indexerService = i;
@@ -41,6 +45,7 @@ namespace Jackett.Controllers
             securityService = s;
             processService = p;
             cacheService = c;
+            logger = l;
         }
 
         private async Task<JToken> ReadPostDataJson()
@@ -128,6 +133,7 @@ namespace Jackett.Controllers
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in SetAdminPassword");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -145,11 +151,13 @@ namespace Jackett.Controllers
                 var indexer = indexerService.GetIndexer((string)postData["indexer"]);
                 var config = await indexer.GetConfigurationForSetup();
                 jsonReply["config"] = config.ToJson();
+                jsonReply["caps"] = indexer.TorznabCaps.CapsToJson();
                 jsonReply["name"] = indexer.DisplayName;
                 jsonReply["result"] = "success";
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in GetConfigForm");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -160,12 +168,13 @@ namespace Jackett.Controllers
         [HttpPost]
         public async Task<IHttpActionResult> Configure()
         {
-            JToken jsonReply = new JObject();
+            var jsonReply = new JObject();
+            IIndexer indexer = null;
             try
             {
                 var postData = await ReadPostDataJson();
                 string indexerString = (string)postData["indexer"];
-                var indexer = indexerService.GetIndexer((string)postData["indexer"]);
+                indexer = indexerService.GetIndexer((string)postData["indexer"]);
                 jsonReply["name"] = indexer.DisplayName;
                 await indexer.ApplyConfiguration(postData["config"]);
                 await indexerService.TestIndexer((string)postData["indexer"]);
@@ -175,9 +184,15 @@ namespace Jackett.Controllers
             {
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
+                var baseIndexer = indexer as BaseIndexer;
+                if (null != baseIndexer)
+                    baseIndexer.ResetBaseConfig();
                 if (ex is ExceptionWithConfigData)
                 {
                     jsonReply["config"] = ((ExceptionWithConfigData)ex).ConfigData.ToJson();
+                } else
+                {
+                    logger.Error(ex, "Exception in Configure");
                 } 
             }
             return Json(jsonReply);
@@ -201,12 +216,14 @@ namespace Jackett.Controllers
                     item["description"] = indexer.DisplayDescription;
                     item["configured"] = indexer.IsConfigured;
                     item["site_link"] = indexer.SiteLink;
+                    item["potatoenabled"] = indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => PotatoController.MOVIE_CATS.Contains(i));
                     items.Add(item);
                 }
                 jsonReply["items"] = items;
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in get_indexers");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -228,6 +245,7 @@ namespace Jackett.Controllers
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in test_indexer");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -247,6 +265,7 @@ namespace Jackett.Controllers
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in delete_indexer");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -269,14 +288,9 @@ namespace Jackett.Controllers
                 jsonReply["config"] = cfg;
                 jsonReply["app_version"] = config.GetVersion();
                 jsonReply["result"] = "success";
-            }
-            catch (CustomException ex)
-            {
-                jsonReply["result"] = "error";
-                jsonReply["error"] = ex.Message;
-            }
-            catch (Exception ex)
+            }catch (Exception ex)
             {
+                logger.Error(ex, "Exception in get_jackett_config");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
@@ -350,6 +364,7 @@ namespace Jackett.Controllers
             }
             catch (Exception ex)
             {
+                logger.Error(ex, "Exception in set_port");
                 jsonReply["result"] = "error";
                 jsonReply["error"] = ex.Message;
             }
diff --git a/src/Jackett/Controllers/DownloadController.cs b/src/Jackett/Controllers/DownloadController.cs
index e40fa1121bce45bbd79d86917ddc9ba38470af40..f2266b60ff56ea28305e0b42827c6a1c75ea659b 100644
--- a/src/Jackett/Controllers/DownloadController.cs
+++ b/src/Jackett/Controllers/DownloadController.cs
@@ -39,7 +39,7 @@ namespace Jackett.Controllers
                 }
 
                 var remoteFile = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
-                var downloadBytes = await indexer.Download(new Uri(remoteFile));
+                var downloadBytes = await indexer.Download(new Uri(remoteFile, UriKind.RelativeOrAbsolute));
 
                 var result = new HttpResponseMessage(HttpStatusCode.OK);
                 result.Content = new ByteArrayContent(downloadBytes);
diff --git a/src/Jackett/Controllers/PotatoController.cs b/src/Jackett/Controllers/PotatoController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b3245f5349ef234f1ebfb25f4c4d700aa7bbb55a
--- /dev/null
+++ b/src/Jackett/Controllers/PotatoController.cs
@@ -0,0 +1,151 @@
+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.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using System.Web.Http;
+
+namespace Jackett.Controllers
+{
+    [AllowAnonymous]
+    public class PotatoController : ApiController
+    {
+        private IIndexerManagerService indexerService;
+        private Logger logger;
+        private IServerService serverService;
+        private ICacheService cacheService;
+        private IWebClient webClient;
+
+        public static readonly int[] MOVIE_CATS = new int[] {2000, 2040, 2030, 2010};
+
+        public PotatoController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c, IWebClient w)
+        {
+            indexerService = i;
+            logger = l;
+            serverService = s;
+            cacheService = c;
+            webClient = w;
+        }
+
+        [HttpGet]
+        public async Task<HttpResponseMessage> Call(string indexerID, [FromUri]TorrentPotatoRequest request)
+        {
+            var indexer = indexerService.GetIndexer(indexerID);
+          
+            var allowBadApiDueToDebug = false;
+#if DEBUG
+            allowBadApiDueToDebug = Debugger.IsAttached;
+#endif
+
+            if (!allowBadApiDueToDebug && !string.Equals(request.passkey, serverService.Config.APIKey, StringComparison.InvariantCultureIgnoreCase))
+            {
+                logger.Warn(string.Format("A request from {0} was made with an incorrect API key.", Request.GetOwinContext().Request.RemoteIpAddress));
+                return Request.CreateResponse(HttpStatusCode.Forbidden, "Incorrect API key");
+            }
+
+            if (!indexer.IsConfigured)
+            {
+                logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
+                return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured.");
+            }
+
+            if (!indexer.TorznabCaps.Categories.Select(c => c.ID).Any(i => MOVIE_CATS.Contains(i))){
+                logger.Warn(string.Format("Rejected a request to {0} which does not support searching for movies.", indexer.DisplayName));
+                return Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer does not support movies.");
+            }
+
+            if (string.IsNullOrWhiteSpace(request.search))
+            {
+                // We are searching by IMDB id so look up the name
+                var response = await webClient.GetString(new Utils.Clients.WebRequest("http://www.omdbapi.com/?type=movie&i=" + request.imdbid));
+                if (response.Status == HttpStatusCode.OK)
+                {
+                    JObject result = JObject.Parse(response.Content);
+                    if (result["Title"] != null)
+                    {
+                        request.search = result["Title"].ToString();
+                    }
+                }
+            }
+
+            var torznabQuery = new TorznabQuery()
+            {
+                ApiKey =  request.passkey,
+                Categories = MOVIE_CATS,
+                SearchTerm = request.search
+            };
+
+            IEnumerable<ReleaseInfo> releases = new List<ReleaseInfo>();
+
+            if (!string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm))
+            {
+                releases = await indexer.PerformQuery(torznabQuery);
+            }
+
+            // Cache non query results
+            if (string.IsNullOrEmpty(torznabQuery.SanitizedSearchTerm))
+            {
+                cacheService.CacheRssResults(indexer.DisplayName, releases);
+            }
+
+            releases = indexer.FilterResults(torznabQuery, releases);
+
+            var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
+            // add Jackett proxy to download links...
+            foreach (var release in releases)
+            {
+                if (release.Link == null || (release.Link.IsAbsoluteUri && release.Link.Scheme == "magnet"))
+                    continue;
+                var originalLink = release.Link;
+                var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/t.torrent";
+                var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexer.ID, encodedLink);
+                release.Link = new Uri(proxyLink);
+            }
+
+            var potatoResponse = new TorrentPotatoResponse();
+
+            foreach(var release in releases)
+            {
+                potatoResponse.results.Add(new TorrentPotatoResponseItem()
+                {
+                    release_name = release.Title + "[" + indexer.DisplayName + "]", // Suffix the indexer so we can see which tracker we are using in CPS as it just says torrentpotato >.>
+                    torrent_id = release.Guid.ToString(),
+                    details_url = release.Comments.ToString(),
+                    download_url = release.Link.ToString(),
+                   // imdb_id = request.imdbid,
+                    freeleech = false,
+                    type = "movie",
+                    size = (long)release.Size/ (1024 * 1024), // This is in MB
+                    leechers = (int)release.Peers - (int)release.Seeders,
+                    seeders = (int)release.Seeders
+                });
+            }
+
+            // Log info
+            if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm))
+            {
+                logger.Info(string.Format("Found {0} torrentpotato releases from {1}", releases.Count(), indexer.DisplayName));
+            }
+            else
+            {
+                logger.Info(string.Format("Found {0} torrentpotato releases from {1} for: {2} {3}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString()));
+            }
+
+            // Force the return as Json
+            return new HttpResponseMessage()
+            {
+                Content = new JsonContent(potatoResponse)
+            };
+        }
+    }
+}
diff --git a/src/Jackett/Controllers/APIController.cs b/src/Jackett/Controllers/TorznabController.cs
similarity index 85%
rename from src/Jackett/Controllers/APIController.cs
rename to src/Jackett/Controllers/TorznabController.cs
index 1ba8fac6db070efc0ff534165d0d13d0dea93779..304921ba27896ec2f01d1f55b8ff9ac800af2fec 100644
--- a/src/Jackett/Controllers/APIController.cs
+++ b/src/Jackett/Controllers/TorznabController.cs
@@ -15,14 +15,14 @@ using System.Web.Http;
 namespace Jackett.Controllers
 {
     [AllowAnonymous]
-    public class APIController : ApiController
+    public class TorznabController : ApiController
     {
         private IIndexerManagerService indexerService;
         private Logger logger;
         private IServerService serverService;
         private ICacheService cacheService;
 
-        public APIController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c)
+        public TorznabController(IIndexerManagerService i, Logger l, IServerService s, ICacheService c)
         {
             indexerService = i;
             logger = l;
@@ -69,14 +69,16 @@ namespace Jackett.Controllers
                 cacheService.CacheRssResults(indexer.DisplayName, releases);
             }
 
+            releases = indexer.FilterResults(torznabQuery, releases);
+
             // Log info
             if (string.IsNullOrWhiteSpace(torznabQuery.SanitizedSearchTerm))
             {
-                logger.Info(string.Format("Found {0} releases from {1}", releases.Length, indexer.DisplayName));
+                logger.Info(string.Format("Found {0} releases from {1}", releases.Count(), indexer.DisplayName));
             }
             else
             {
-                logger.Info(string.Format("Found {0} releases from {1} for: {2}", releases.Length, indexer.DisplayName, torznabQuery.SanitizedSearchTerm));
+                logger.Info(string.Format("Found {0} releases from {1} for: {2} {3}", releases.Count(), indexer.DisplayName, torznabQuery.SanitizedSearchTerm, torznabQuery.GetEpisodeSearchString()));
             }
 
             var severUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
@@ -85,20 +87,20 @@ namespace Jackett.Controllers
             {
                 Title = indexer.DisplayName,
                 Description = indexer.DisplayDescription,
-                Link = indexer.SiteLink,
+                Link = new Uri(indexer.SiteLink),
                 ImageUrl = new Uri(severUrl + "logos/" + indexer.ID + ".png"),
                 ImageTitle = indexer.DisplayName,
-                ImageLink = indexer.SiteLink,
+                ImageLink = new Uri(indexer.SiteLink),
                 ImageDescription = indexer.DisplayName
             });
 
             // add Jackett proxy to download links...
             foreach (var release in releases)
             {
-                if (release.Link == null || release.Link.Scheme == "magnet")
+                if (release.Link == null || (release.Link.IsAbsoluteUri && release.Link.Scheme == "magnet"))
                     continue;
                 var originalLink = release.Link;
-                var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/download.torrent";
+                var encodedLink = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(originalLink.ToString())) + "/t.torrent";
                 var proxyLink = string.Format("{0}api/{1}/download/{2}", severUrl, indexer.ID, encodedLink);
                 release.Link = new Uri(proxyLink);
             }
diff --git a/src/Jackett/CurlHelper.cs b/src/Jackett/CurlHelper.cs
index 22df91bddcb3a247f01b008a06a2c3461fc0a5d3..99d066fa35374f033e1b6136d7a6a7dee3ea8b9d 100644
--- a/src/Jackett/CurlHelper.cs
+++ b/src/Jackett/CurlHelper.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
 using System.Net.Http.Headers;
 using Jackett.Utils;
 using System.Net;
+using System.Threading;
 
 namespace Jackett
 {
@@ -16,23 +17,12 @@ namespace Jackett
     {
         private static readonly object instance = new object();
 
-        static CurlHelper()
-        {
-            Engine.Logger.Debug("LibCurl init" + Curl.GlobalInit(CurlInitFlag.All).ToString());
-            Engine.Logger.Debug("LibCurl version " + Curl.Version);
-        }
-
         public class CurlRequest
         {
-
             public string Url { get; private set; }
-
             public string Cookies { get; private set; }
-
             public string Referer { get; private set; }
-
             public HttpMethod Method { get; private set; }
-
             public Dictionary<string, string> PostData { get; set; }
 
             public CurlRequest(HttpMethod method, string url, string cookies = null, string referer = null)
@@ -46,49 +36,17 @@ namespace Jackett
 
         public class CurlResponse
         {
-            public Dictionary<string, string> Headers { get; private set; }
             public List<string[]> HeaderList { get; private set; }
             public byte[] Content { get; private set; }
             public HttpStatusCode Status { get; private set;}
-            public Dictionary<string, string> Cookies { get; private set; }
-            public List<string> CookiesFlat { get { return Cookies.Select(c => c.Key + "=" + c.Value).ToList(); } }
-            public string CookieHeader { get { return string.Join("; ", CookiesFlat); } }
+            public string Cookies { set; get; }
 
-            public CurlResponse(List<string[]> headers, byte[] content, HttpStatusCode s)
+            public CurlResponse(List<string[]> headers, byte[] content, HttpStatusCode s, string cookies)
             {
-                Headers = new Dictionary<string, string>();
-                Cookies = new Dictionary<string, string>();
                 HeaderList = headers;
                 Content = content;
                 Status = s;
-                foreach (var h in headers)
-                {
-                    Headers[h[0]] = h[1];
-                }
-            }
-
-            public void AddCookiesFromHeaderValue(string cookieHeaderValue)
-            {
-                var rawCookies = cookieHeaderValue.Split(';');
-                foreach (var rawCookie in rawCookies)
-                {
-                    var parts = rawCookie.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
-                    if (parts.Length == 1)
-                        Cookies[rawCookie.Trim()] = string.Empty;
-                    else
-                        Cookies[parts[0].Trim()] = parts[1].Trim();
-                }
-            }
-
-            public void AddCookiesFromHeaders(List<string[]> headers)
-            {
-                foreach (var h in headers)
-                {
-                    if (h[0] == "set-cookie")
-                    {
-                        AddCookiesFromHeaderValue(h[1]);
-                    }
-                }
+                Cookies = cookies;
             }
         }
 
@@ -107,36 +65,14 @@ namespace Jackett
             return result;
         }
 
-        private static async Task<CurlResponse> FollowRedirect(string url, CurlResponse response)
-        {
-            var uri = new Uri(url);
-            string redirect;
-            if (response.Headers.TryGetValue("location", out redirect))
-            {
-                string cookie = response.CookieHeader;
-                if (!redirect.StartsWith("http://") && !redirect.StartsWith("https://"))
-                {
-                    if (redirect.StartsWith("/"))
-                        redirect = string.Format("{0}://{1}{2}", uri.Scheme, uri.Host, redirect);
-                    else
-                        redirect = string.Format("{0}://{1}/{2}", uri.Scheme, uri.Host, redirect);
-                }
-                var newRedirect = await GetAsync(redirect, cookie);
-                foreach (var c in response.Cookies)
-                    newRedirect.Cookies[c.Key] = c.Value;
-                newRedirect.AddCookiesFromHeaders(response.HeaderList);
-                return newRedirect;
-            }
-            else
-                return response;
-        }
-
-
         public static async Task<CurlResponse> PerformCurlAsync(CurlRequest curlRequest)
         {
             return await Task.Run(() => PerformCurl(curlRequest));
         }
 
+        public delegate void ErrorMessage(string s);
+        public static ErrorMessage OnErrorMessage;
+
         public static CurlResponse PerformCurl(CurlRequest curlRequest)
         {
             lock (instance)
@@ -151,11 +87,13 @@ namespace Jackett
                     easy.UserAgent = BrowserUtil.ChromeUserAgent;
                     easy.FollowLocation = false;
                     easy.ConnectTimeout  = 20;
+
                     easy.WriteFunction = (byte[] buf, int size, int nmemb, object data) =>
                     {
                         contentBuffers.Add(buf);
                         return size * nmemb;
                     };
+
                     easy.HeaderFunction = (byte[] buf, int size, int nmemb, object extraData) =>
                     {
                         headerBuffers.Add(buf);
@@ -176,7 +114,25 @@ namespace Jackett
                         easy.PostFieldSize = Encoding.UTF8.GetByteCount(postString);
                     }
 
+                    if (Startup.DoSSLFix == true)
+                    {
+                        // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio
+                        // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html
+                        easy.SslCipherList = SSLFix.CipherList;
+                        easy.FreshConnect = true;
+                        easy.ForbidReuse = true;
+                    }
+
                     easy.Perform();
+
+                    if(easy.LastErrorCode != CurlCode.Ok)
+                    {
+                        var message = "Error " + easy.LastErrorCode.ToString() + " " + easy.LastErrorDescription;
+                        if (null != OnErrorMessage)
+                            OnErrorMessage(message);
+                        else
+                            Console.WriteLine(message);
+                    }
                 }
 
                 var headerBytes = Combine(headerBuffers.ToArray());
@@ -185,10 +141,14 @@ namespace Jackett
                 var headers = new List<string[]>();
                 var headerCount = 0;
                 HttpStatusCode status = HttpStatusCode.InternalServerError;
+                var cookieBuilder = new StringBuilder();
                 foreach (var headerPart in headerParts)
                 {
                     if (headerCount == 0)
                     {
+                        var split = headerPart.Split(' ');
+                        if (split.Length < 2)
+                            throw new Exception("HTTP Header missing");
                         var responseCode = int.Parse(headerPart.Split(' ')[1]);
                         status = (HttpStatusCode)responseCode;
                     }
@@ -197,7 +157,17 @@ namespace Jackett
                         var keyVal = headerPart.Split(new char[] { ':' }, 2);
                         if (keyVal.Length > 1)
                         {
-                            headers.Add(new[] { keyVal[0].ToLower().Trim(), keyVal[1].Trim() });
+                            var key = keyVal[0].ToLower().Trim();
+                            var value = keyVal[1].Trim();
+
+                            if (key == "set-cookie")
+                            {
+                                cookieBuilder.AppendFormat("{0} ", value.Substring(0, value.IndexOf(';') + 1));
+                            }
+                            else
+                            {
+                                headers.Add(new[] { key, value });
+                            }
                         }
                     }
 
@@ -205,12 +175,7 @@ namespace Jackett
                 }
 
                 var contentBytes = Combine(contentBuffers.ToArray());
-
-                var curlResponse = new CurlResponse(headers, contentBytes, status);
-                if (!string.IsNullOrEmpty(curlRequest.Cookies))
-                    curlResponse.AddCookiesFromHeaderValue(curlRequest.Cookies);
-                curlResponse.AddCookiesFromHeaders(headers);
-
+                var curlResponse = new CurlResponse(headers, contentBytes, status, cookieBuilder.ToString().TrimEnd());
                 return curlResponse;
             }
         }
diff --git a/src/Jackett/Engine.cs b/src/Jackett/Engine.cs
index 389f9f5543ce09614aba8be96f9e20d03f71077c..19a2bc8748b1be62c4ad0fc78a0d1c41154460c6 100644
--- a/src/Jackett/Engine.cs
+++ b/src/Jackett/Engine.cs
@@ -119,7 +119,7 @@ namespace Jackett
             logFile.FileName = Path.Combine(ConfigurationService.GetAppDataFolderStatic(), "log.txt");
             logFile.ArchiveFileName = "log.{#####}.txt";
             logFile.ArchiveAboveSize = 500000;
-            logFile.MaxArchiveFiles = 1;
+            logFile.MaxArchiveFiles = 5;
             logFile.KeepFileOpen = false;
             logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence;
             var logFileRule = new LoggingRule("*", logLevel, logFile);
diff --git a/src/Jackett/ExceptionWithConfigData.cs b/src/Jackett/ExceptionWithConfigData.cs
index 9280d5f14e8226ceb8632c7672681e5d036ca3db..9c4c7125e6ef6ea7bcc8e88ad54529d8fbed3019 100644
--- a/src/Jackett/ExceptionWithConfigData.cs
+++ b/src/Jackett/ExceptionWithConfigData.cs
@@ -18,11 +18,4 @@ namespace Jackett
         }
 
     }
-
-    public class CustomException : Exception
-    {
-        public CustomException(string message)
-            : base(message)
-        { }
-    }
 }
diff --git a/src/Jackett/Indexers/AlphaRatio.cs b/src/Jackett/Indexers/AlphaRatio.cs
index 5a39584c5695686ad14d784359c5dee147990850..dd4e7f16a4c66604da590126d5eedd6ad48154d7 100644
--- a/src/Jackett/Indexers/AlphaRatio.cs
+++ b/src/Jackett/Indexers/AlphaRatio.cs
@@ -19,100 +19,48 @@ namespace Jackett.Indexers
 {
     public class AlphaRatio : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private readonly string DownloadUrl = "";
-        private readonly string GuidUrl = "";
-
-        private IWebClient webclient;
-        private string cookieHeader = "";
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
+        private string SearchUrl { get { return SiteLink + "ajax.php?action=browse&searchstr="; } }
+        private string DownloadUrl { get { return SiteLink + "torrents.php?action=download&id="; } }
+        private string GuidUrl { get { return SiteLink + "torrents.php?torrentid="; } }
 
         public AlphaRatio(IIndexerManagerService i, IWebClient w, Logger l)
             : base(name: "AlphaRatio",
                 description: "Legendary",
-                link: new Uri("https://alpharatio.cc"),
+                link: "https://alpharatio.cc/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-            LoginUrl = SiteLink + "login.php";
-            SearchUrl = SiteLink + "ajax.php?action=browse&searchstr=";
-            DownloadUrl = SiteLink + "torrents.php?action=download&id=";
-            GuidUrl = SiteLink + "torrents.php?torrentid=";
             webclient = w;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
             var incomingConfig = new ConfigurationDataBasicLogin();
             incomingConfig.LoadValuesFromJson(configJson);
-
             var pairs = new Dictionary<string, string> {
-				{ "username", incomingConfig.Username.Value },
-				{ "password", incomingConfig.Password.Value },
-				{ "login", "Login" },
-			    { "keeplogged", "1" }
-			};
+                { "username", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value },
+                { "login", "Login" },
+                { "keeplogged", "1" }
+            };
 
             // Do the login
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                PostData = pairs,
-                Referer = LoginUrl,
-                Type = RequestType.POST,
-                Url = LoginUrl
-            });
-
-            cookieHeader = response.Cookies;
-
-            if (response.Status == HttpStatusCode.Found || response.Status == HttpStatusCode.Redirect || response.Status == HttpStatusCode.RedirectMethod)
-            {
-                response = await webclient.GetString(new Utils.Clients.WebRequest()
-                {
-                    Url = SiteLink.ToString(),
-                    Referer = LoginUrl.ToString(),
-                    Cookies = cookieHeader
-                });
-            }
-
-            if (!response.Content.Contains("logout.php?"))
-            {
-                CQ dom = response.Content;
-                dom["#loginform > table"].Remove();
-                var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " ");
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookie_header"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        HttpRequestMessage CreateHttpRequest(Uri uri)
-        {
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Post;
-            message.RequestUri = uri;
-            message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
-            return message;
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookie_header"];
-            if (!string.IsNullOrEmpty(cookieHeader))
-            {
-                IsConfigured = true;
-            }
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, string.Empty, true, SiteLink);
+            await ConfigureIfOK(response.Cookies, response.Content!=null && response.Content.Contains("logout.php?"), () =>
+             {
+                 CQ dom = response.Content;
+                 dom["#loginform > table"].Remove();
+                 var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " ");
+                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+             });
         }
 
         void FillReleaseInfoFromJson(ReleaseInfo release, JObject r)
@@ -126,24 +74,16 @@ namespace Jackett.Indexers
             release.Link = new Uri(DownloadUrl + id);
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
-
-            var results = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Cookies = cookieHeader,
-                Type = RequestType.GET,
-                Url = episodeSearchUrl
-            });
-
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
             try
             {
-
-                var json = JObject.Parse(results.Content);
+                var json = JObject.Parse(response.Content);
                 foreach (JObject r in json["response"]["results"])
                 {
                     DateTime pubDate = DateTime.MinValue;
@@ -179,10 +119,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results.Content, ex);
+                OnParseError(response.Content, ex);
             }
 
-            return releases.ToArray();
+            return releases;
         }
 
         static DateTime UnixTimestampToDateTime(double unixTime)
@@ -191,17 +131,5 @@ namespace Jackett.Indexers
             long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
             return new DateTime(unixStart.Ticks + unixTimeStampInTicks);
         }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
-            });
-
-            return response.Content;
-        }
-
     }
 }
diff --git a/src/Jackett/Indexers/AnimeBytes.cs b/src/Jackett/Indexers/AnimeBytes.cs
index 613888f987ad304b171032eaca7e8fcc4477644b..b8ae9758b6b58d1f85ea8a53b751c9ec089a950c 100644
--- a/src/Jackett/Indexers/AnimeBytes.cs
+++ b/src/Jackett/Indexers/AnimeBytes.cs
@@ -1,5 +1,6 @@
 using CsQuery;
 using Jackett.Models;
+using Jackett.Models.IndexerConfig;
 using Jackett.Services;
 using Jackett.Utils;
 using Jackett.Utils.Clients;
@@ -22,50 +23,24 @@ namespace Jackett.Indexers
 {
     public class AnimeBytes : BaseIndexer, IIndexer
     {
-        class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin
-        {
-            public BoolItem IncludeRaw { get; private set; }
-            public DisplayItem DateWarning { get; private set; }
-
-            public ConfigurationDataBasicLoginAnimeBytes()
-                : 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" };
-            }
-
-            public override Item[] GetItems()
-            {
-                return new Item[] { Username, Password, IncludeRaw, DateWarning };
-            }
-        }
-
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
+        private string LoginUrl { get { return SiteLink + "user/login"; } }
+        private string SearchUrl { get { return SiteLink + "torrents.php?filter_cat[1]=1"; } }
         public bool AllowRaws { get; private set; }
 
-        private IWebClient webclient;
-        private string cookieHeader = "";
-
         public AnimeBytes(IIndexerManagerService i, IWebClient client, Logger l)
             : base(name: "AnimeBytes",
-                description: "The web's best Chinese cartoons",
-                link: new Uri("https://animebytes.tv"),
-                caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                link: "https://animebytes.tv/",
+                description: "Powered by Tentacles",
                 manager: i,
+                client: client,
+                caps: new TorznabCapabilities(TorznabCatType.Anime),
                 logger: l)
         {
-            TorznabCaps.Categories.Clear();
-            TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" });
-            LoginUrl = SiteLink + "user/login";
-            SearchUrl = SiteLink + "torrents.php?filter_cat[1]=1";
-            webclient = client;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLoginAnimeBytes();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLoginAnimeBytes());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -73,6 +48,10 @@ namespace Jackett.Indexers
             var config = new ConfigurationDataBasicLoginAnimeBytes();
             config.LoadValuesFromJson(configJson);
 
+            lock (cache)
+            {
+                cache.Clear();
+            }
 
             // Get the login form as we need the CSRF Token
             var loginPage = await webclient.GetString(new Utils.Clients.WebRequest()
@@ -85,46 +64,36 @@ namespace Jackett.Indexers
 
             // Build login form
             var pairs = new Dictionary<string, string> {
-                  { "csrf_token", csrfToken.Attr("value") },
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value },
-                { "keeplogged_sent", "true" },
-                { "keeplogged", "on" },
-                { "login", "Log In!" }
+                    { "csrf_token", csrfToken.Attr("value") },
+				    { "username", config.Username.Value },
+				    { "password", config.Password.Value },
+                    { "keeplogged_sent", "true" },
+                    { "keeplogged", "on" },
+                    { "login", "Log In!" }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
             // Do the login
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
+            var request = new Utils.Clients.WebRequest()
             {
                 Cookies = loginPage.Cookies,
-                 PostData = pairs,
-                 Referer = LoginUrl,
-                 Type = RequestType.POST,
-                 Url = LoginUrl
-            });
+                PostData = pairs,
+                Referer = LoginUrl,
+                Type = RequestType.POST,
+                Url = LoginUrl
+            };
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null);
 
             // Follow the redirect
-            if (response.Status == HttpStatusCode.RedirectMethod)
-            {
-                cookieHeader = response.Cookies;
-                response = await webclient.GetString(new Utils.Clients.WebRequest()
-                {
-                    Url = SearchUrl,
-                    PostData = pairs,
-                    Referer = SiteLink.ToString(),
-                    Cookies = cookieHeader
-                });
-            }
+            await FollowIfRedirect(response, request.Url, SearchUrl);
 
-            if (!response.Content.Contains("/user/logout"))
+            if (!(response.Content != null && response.Content.Contains("/user/logout")))
             {
                 // Their login page appears to be broken and just gives a 500 error.
                 throw new ExceptionWithConfigData("Failed to login, 6 failed attempts will get you banned for 6 hours.", (ConfigurationData)config);
             }
             else
             {
+                cookieHeader = response.Cookies;
                 AllowRaws = config.IncludeRaw.Value;
                 var configSaveData = new JObject();
                 configSaveData["cookies"] = cookieHeader;
@@ -134,7 +103,7 @@ namespace Jackett.Indexers
             }
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(JToken jsonConfig)
         {
             // The old config used an array - just fail to load it
             if (!(jsonConfig["cookies"] is JArray))
@@ -145,24 +114,7 @@ namespace Jackett.Indexers
             }
         }
 
-
-        private string Hash(string input)
-        {
-            // Use input string to calculate MD5 hash
-            MD5 md5 = System.Security.Cryptography.MD5.Create();
-            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
-            byte[] hashBytes = md5.ComputeHash(inputBytes);
-
-            // Convert the byte array to hexadecimal string
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < hashBytes.Length; i++)
-            {
-                sb.Append(hashBytes[i].ToString("X2"));
-            }
-            return sb.ToString();
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             // The result list
             var releases = new List<ReleaseInfo>();
@@ -175,7 +127,7 @@ namespace Jackett.Indexers
             return releases.ToArray();
         }
 
-        public async Task<ReleaseInfo[]> GetResults(string searchTerm)
+        public async Task<IEnumerable<ReleaseInfo>> GetResults(string searchTerm)
         {
             // This tracker only deals with full seasons so chop off the episode/season number if we have it D:
             if (!string.IsNullOrWhiteSpace(searchTerm))
@@ -207,12 +159,7 @@ namespace Jackett.Indexers
             }
 
             // Get the content from the tracker
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Cookies = cookieHeader,
-                Url = queryUrl,
-                Type = RequestType.GET
-            });
+            var response = await RequestStringWithCookiesAndRetry(queryUrl);
             CQ dom = response.Content;
 
             // Parse
@@ -310,9 +257,9 @@ namespace Jackett.Indexers
                                 release.PublishDate = release.PublishDate.AddDays(Math.Min(DateTime.Now.DayOfYear, 365) - 1);
 
                                 var infoLink = links.Get(1);
-                                release.Comments = new Uri(SiteLink + "/" + infoLink.Attributes.GetAttribute("href"));
-                                release.Guid = new Uri(SiteLink + "/" + infoLink.Attributes.GetAttribute("href") + "&nh=" + Hash(title)); // Sonarr should dedupe on this url - allow a url per name.
-                                release.Link = new Uri(SiteLink + "/" + downloadLink.Attributes.GetAttribute("href"));
+                                release.Comments = new Uri(SiteLink  + infoLink.Attributes.GetAttribute("href"));
+                                release.Guid = new Uri(SiteLink + infoLink.Attributes.GetAttribute("href") + "&nh=" + StringUtil.Hash(title)); // Sonarr should dedupe on this url - allow a url per name.
+                                release.Link = new Uri(downloadLink.Attributes.GetAttribute("href"), UriKind.Relative);
 
                                 // We dont actually have a release name >.> so try to create one
                                 var releaseTags = infoLink.InnerText.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
@@ -380,14 +327,16 @@ namespace Jackett.Indexers
                 cache.Add(new CachedQueryResult(searchTerm, releases));
             }
 
-            return releases.Select(s => (ReleaseInfo)s.Clone()).ToArray();
+            return releases.Select(s => (ReleaseInfo)s.Clone());
         }
 
-        public async Task<byte[]> Download(Uri link)
+
+        public async override Task<byte[]> Download(Uri link)
         {
+            // The urls for this tracker are quite long so append the domain after the incoming request.
             var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
             {
-                Url = link.ToString(),
+                Url = SiteLink + link.ToString(),
                 Cookies = cookieHeader
             });
 
diff --git a/src/Jackett/Indexers/BB.cs b/src/Jackett/Indexers/BB.cs
index 9e7c39251a89fdd54edadf6c66f251d75c5fad6d..7ba01fcf2de1980c9637196e5ecd70cddc8d47ea 100644
--- a/src/Jackett/Indexers/BB.cs
+++ b/src/Jackett/Indexers/BB.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -19,61 +20,42 @@ namespace Jackett.Indexers
 
     public class BB : BaseIndexer, IIndexer
     {
-        private readonly string BaseUrl = "";
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
+        private string BaseUrl {  get { return StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw=="); } }
+        private Uri BaseUri { get { return new Uri(BaseUrl); } }
+        private string LoginUrl { get { return BaseUri + "login.php"; } }
+        private string SearchUrl { get { return BaseUri + "torrents.php?searchstr={0}&searchtags=&tags_type=0&order_by=s3&order_way=desc&disablegrouping=1&filter_cat%5B10%5D=1"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public BB(IIndexerManagerService i, Logger l)
+        public BB(IIndexerManagerService i, Logger l, IWebClient w)
             : base(name: "bB",
                 description: "bB",
-                link: new Uri("http://www.reddit.com/r/baconbits"),
+                link: "http://www.reddit.com/r/baconbits/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-
-            BaseUrl = StringUtil.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3Jn");
-            LoginUrl = BaseUrl + "/login.php";
-            SearchUrl = BaseUrl + "/torrents.php?searchstr={0}&searchtags=&tags_type=0&order_by=s3&order_way=desc&disablegrouping=1&filter_cat%5B10%5D=1";
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value },
+                { "username", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value },
                 { "keeplogged", "1" },
                 { "login", "Log In!" }
-			};
-
-            var content = new FormUrlEncodedContent(pairs);
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
+            };
 
-            if (!responseContent.Contains("logout.php"))
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink);
+            await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 var messageEl = dom["#loginform"];
                 var messages = new List<string>();
                 for (var i = 0; i < 13; i++)
@@ -82,35 +64,22 @@ namespace Jackett.Indexers
                     messages.Add(child.Cq().Text().Trim());
                 }
                 var message = string.Join(" ", messages);
-                throw new ExceptionWithConfigData(message, (ConfigurationData)config);
-            }
-            else
-            {
-
-                var configSaveData = new JObject();
-                cookies.DumpToJson(BaseUrl, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(message, (ConfigurationData)incomingConfig);
 
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookies.FillFromJson(new Uri(BaseUrl), jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             List<ReleaseInfo> releases = new List<ReleaseInfo>();
 
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
+
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 var rows = dom["#torrent_table > tbody > tr.torrent"];
                 foreach (var row in rows)
                 {
@@ -143,15 +112,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
-            return releases.ToArray();
+            return releases;
         }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
-        }
-
     }
 }
diff --git a/src/Jackett/Indexers/BakaBT.cs b/src/Jackett/Indexers/BakaBT.cs
index 647c6f878b3e265d76c746ecc53a8fc6bf86867b..5680e1748c4899366aaeb40b5db1f44ceaef4e97 100644
--- a/src/Jackett/Indexers/BakaBT.cs
+++ b/src/Jackett/Indexers/BakaBT.cs
@@ -18,31 +18,23 @@ namespace Jackett.Indexers
 {
     public class BakaBT : BaseIndexer, IIndexer
     {
-        public readonly string SearchUrl = "";
-        public readonly string LoginUrl = "";
-        private string cookieHeader = "";
-        private IWebClient webclient;
+        public string SearchUrl { get { return SiteLink + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&c1=1&reorder=1&q="; } }
+        public string LoginUrl { get { return SiteLink + "login.php"; } }
 
         public BakaBT(IIndexerManagerService i, IWebClient wc, Logger l)
             : base(name: "BakaBT",
                 description: "Anime Community",
-                link: new Uri("http://bakabt.me/"),
-                caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                link: "http://bakabt.me/",
+                caps: new TorznabCapabilities(TorznabCatType.Anime),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            TorznabCaps.Categories.Clear();
-            TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" });
-
-            SearchUrl = SiteLink + "browse.php?only=0&hentai=1&incomplete=1&lossless=1&hd=1&multiaudio=1&bonus=1&c1=1&reorder=1&q=";
-            LoginUrl = SiteLink + "login.php";
-            webclient = wc;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>((ConfigurationData)config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -62,69 +54,32 @@ namespace Jackett.Indexers
                 { "returnto", "/index.php" }
             };
 
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = LoginUrl,
-                PostData = pairs,
-                Referer = SiteLink.ToString(),
-                Type = RequestType.POST,
-                Cookies = loginForm.Cookies
-            });
-
-            cookieHeader = response.Cookies;
-            if (response.Status == HttpStatusCode.Found)
-            {
-                response = await webclient.GetString(new Utils.Clients.WebRequest()
-                {
-                    Url = SearchUrl,
-                    Cookies = cookieHeader
-                });
-            }
-
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginForm.Cookies, true, null, SiteLink);
             var responseContent = response.Content;
-
-            if (!responseContent.Contains("<a href=\"logout.php\">Logout</a>"))
-            {
-                CQ dom = responseContent;
-                var messageEl = dom[".error"].First();
-                var errorMessage = messageEl.Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookies"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookies"];
-            IsConfigured = true;
+            await ConfigureIfOK(response.Cookies, responseContent.Contains("<a href=\"logout.php\">Logout</a>"), () =>
+             {
+                 CQ dom = responseContent;
+                 var messageEl = dom[".error"].First();
+                 var errorMessage = messageEl.Text().Trim();
+                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
+             });
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
 
             // This tracker only deals with full seasons so chop off the episode/season number if we have it D:
-            if (!string.IsNullOrWhiteSpace(query.SanitizedSearchTerm))
+            if (!string.IsNullOrWhiteSpace(query.SearchTerm))
             {
-                var splitindex = query.SanitizedSearchTerm.LastIndexOf(' ');
+                var splitindex = query.SearchTerm.LastIndexOf(' ');
                 if (splitindex > -1)
-                    query.SanitizedSearchTerm = query.SanitizedSearchTerm.Substring(0, splitindex);
+                    query.SearchTerm = query.SearchTerm.Substring(0, splitindex);
             }
 
             var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm;
             var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
-
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = episodeSearchUrl,
-                Cookies = cookieHeader
-            });
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
             try
             {
@@ -215,17 +170,12 @@ namespace Jackett.Indexers
                 OnParseError(response.Content, ex);
             }
 
-            return releases.ToArray();
+            return releases;
         }
 
-        public async Task<byte[]> Download(Uri link)
+        public override async Task<byte[]> Download(Uri link)
         {
-            var downloadPage = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
-            });
-
+            var downloadPage = await RequestStringWithCookies(link.ToString());
             CQ dom = downloadPage.Content;
             var downloadLink = dom.Find(".download_link").First().Attr("href");
 
@@ -234,14 +184,7 @@ namespace Jackett.Indexers
                 throw new Exception("Unable to find download link.");
             }
 
-            downloadLink = SiteLink + downloadLink;
-
-            var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Url = downloadLink,
-                Cookies = cookieHeader
-            });
-
+            var response = await RequestBytesWithCookies(SiteLink + downloadLink);
             return response.Content;
         }
     }
diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs
index 9151e36dde5f2235b880266a13888f4d0cedc75c..50999b6a6d67ca96eadfbe26463f308049c31433 100644
--- a/src/Jackett/Indexers/BaseIndexer.cs
+++ b/src/Jackett/Indexers/BaseIndexer.cs
@@ -8,39 +8,69 @@ using Newtonsoft.Json.Linq;
 using NLog;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
+using AutoMapper;
+using System.Threading;
 
 namespace Jackett.Indexers
 {
     public abstract class BaseIndexer
     {
+        public string SiteLink { get; private set; }
         public string DisplayDescription { get; private set; }
         public string DisplayName { get; private set; }
         public string ID { get { return GetIndexerID(GetType()); } }
 
         public bool IsConfigured { get; protected set; }
-        public Uri SiteLink { get; private set; }
-
         public TorznabCapabilities TorznabCaps { get; private set; }
-
         protected Logger logger;
         protected IIndexerManagerService indexerService;
-
         protected static List<CachedQueryResult> cache = new List<CachedQueryResult>();
         protected static readonly TimeSpan cacheTime = new TimeSpan(0, 9, 0);
+        protected IWebClient webclient;
+        protected string cookieHeader = "";
 
-        public static string GetIndexerID(Type type)
-        {
-            return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant());
-        }
+        private List<CategoryMapping> categoryMapping = new List<CategoryMapping>();
 
-        public BaseIndexer(string name, string description, Uri link, TorznabCapabilities caps, IIndexerManagerService manager,Logger logger)
+        public BaseIndexer(string name, string link, string description, IIndexerManagerService manager, IWebClient client, Logger logger, TorznabCapabilities caps = null)
         {
+            if (!link.EndsWith("/"))
+                throw new Exception("Site link must end with a slash.");
+
             DisplayName = name;
             DisplayDescription = description;
             SiteLink = link;
-            TorznabCaps = caps;
             this.logger = logger;
             indexerService = manager;
+            webclient = client;
+
+            if (caps == null)
+                caps = TorznabCapsUtil.CreateDefaultTorznabTVCaps();
+            TorznabCaps = caps;
+        }
+
+        protected int MapTrackerCatToNewznab(string input)
+        {
+            if (null != input) {
+                input = input.ToLowerInvariant();
+                var mapping = categoryMapping.Where(m => m.TrackerCategory == input).FirstOrDefault();
+                if(mapping!= null)
+                {
+                    return mapping.NewzNabCategory;
+                }
+            }
+            return 0;
+        }
+
+        public static string GetIndexerID(Type type)
+        {
+            return StringUtil.StripNonAlphaNumeric(type.Name.ToLowerInvariant());
+        }
+
+        public void ResetBaseConfig()
+        {
+            cookieHeader = string.Empty;
+            IsConfigured = false;
         }
 
         protected void SaveConfig(JToken config)
@@ -64,5 +94,281 @@ namespace Jackett.Indexers
                 cache.Remove(expired);
             }
         }
+
+        protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        {
+            var byteResult = new WebClientByteResult();
+            // Map to byte
+            Mapper.Map(response, byteResult);
+            await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies);
+            // Map to string
+            Mapper.Map(byteResult, response);
+        }
+
+        protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        {
+            // Follow up  to 5 redirects
+            for (int i = 0; i < 5; i++)
+            {
+                if (!response.IsRedirect)
+                    break;
+                await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies);
+            }
+        }
+
+        private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        {
+            if (incomingResponse.IsRedirect)
+            {
+                // Do redirect
+                var redirectedResponse = await webclient.GetBytes(new WebRequest()
+                {
+                    Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo,
+                    Referer = referrer,
+                    Cookies = overrideCookies ?? cookieHeader
+                });
+                Mapper.Map(redirectedResponse, incomingResponse);
+            }
+        }
+
+
+        protected void LoadCookieHeaderAndConfigure(JToken jsonConfig)
+        {
+            cookieHeader = (string)jsonConfig["cookie_header"];
+            if (!string.IsNullOrEmpty(cookieHeader))
+            {
+                IsConfigured = true;
+            }
+            else
+            {
+                // Legacy cookie key
+                var jcookes = jsonConfig["cookies"];
+                if (jcookes is JArray) {
+                    var array = (JArray)jsonConfig["cookies"];
+                    cookieHeader = string.Empty;
+                    for (int i = 0; i < array.Count; i++)
+                    {
+                        if (i != 0)
+                            cookieHeader += "; ";
+                        cookieHeader += array[i];
+                    }
+                }
+                else
+                    cookieHeader = (string)jsonConfig["cookies"];
+               
+                if (!string.IsNullOrEmpty(cookieHeader))
+                {
+                    IsConfigured = true;
+                }
+            }
+        }
+
+        protected void SaveCookieHeaderAndConfigure()
+        {
+            var configSaveData = new JObject();
+            configSaveData["cookie_header"] = cookieHeader;
+            SaveConfig(configSaveData);
+            IsConfigured = !string.IsNullOrEmpty(cookieHeader);
+        }
+
+        public virtual void LoadFromSavedConfiguration(JToken jsonConfig)
+        {
+            LoadCookieHeaderAndConfigure(jsonConfig);
+        }
+
+        public async virtual Task<byte[]> Download(Uri link)
+        {
+            var response = await RequestBytesWithCookiesAndRetry(link.ToString());
+            return response.Content;
+        }
+
+        protected async Task<WebClientByteResult> RequestBytesWithCookiesAndRetry(string url, string cookieOverride = null)
+        {
+            Exception lastException = null;
+            for (int i = 0; i < 3; i++)
+            {
+                try
+                {
+                    return await RequestBytesWithCookies(url, cookieOverride);
+                }
+                catch (Exception e)
+                {
+                    logger.Error(string.Format("On attempt {0} downloading from {1}: {2}", (i + 1), DisplayName, e.Message));
+                    lastException = e;
+                    await Task.Delay(500);
+                }
+            }
+
+            throw lastException;
+        }
+
+        protected async Task<WebClientStringResult> RequestStringWithCookies(string url, string cookieOverride = null, string referer = null)
+        {
+            var request = new Utils.Clients.WebRequest()
+            {
+                Url = url,
+                Type = RequestType.GET,
+                Cookies = cookieHeader,
+                Referer = referer
+            };
+
+            if (cookieOverride != null)
+                request.Cookies = cookieOverride;
+            return await webclient.GetString(request);
+        }
+
+        protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null)
+        {
+            Exception lastException = null;
+            for (int i = 0; i < 3; i++)
+            {
+                try
+                {
+                    return await RequestStringWithCookies(url, cookieOverride, referer);
+                }
+                catch (Exception e)
+                {
+                    logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message));
+                    lastException= e;
+                    await Task.Delay(500);
+                }
+            }
+
+            throw lastException;
+        }
+
+        protected async Task<WebClientByteResult> RequestBytesWithCookies(string url, string cookieOverride = null)
+        {
+            var request = new Utils.Clients.WebRequest()
+            {
+                Url = url,
+                Type = RequestType.GET,
+                Cookies = cookieOverride ?? cookieHeader
+            };
+
+            if (cookieOverride != null)
+                request.Cookies = cookieOverride;
+            return await webclient.GetBytes(request);
+        }
+
+        protected async Task<WebClientStringResult> PostDataWithCookies(string url, Dictionary<string, string> data, string cookieOverride = null)
+        {
+            var request = new Utils.Clients.WebRequest()
+            {
+                Url = url,
+                Type = RequestType.POST,
+                Cookies = cookieOverride ?? cookieHeader,
+                PostData = data
+            };
+            return await webclient.GetString(request);
+        }
+
+        protected async Task<WebClientStringResult> PostDataWithCookiesAndRetry(string url, Dictionary<string, string> data, string cookieOverride = null)
+        {
+            Exception lastException = null;
+            for (int i = 0; i < 3; i++)
+            {
+                try
+                {
+                    return await PostDataWithCookies(url,data,cookieOverride);
+                }
+                catch (Exception e)
+                {
+                    logger.Error(string.Format("On attempt {0} checking for results from {1}: {2}", (i + 1), DisplayName, e.Message));
+                    lastException = e;
+                    await Task.Delay(500);
+                }
+            }
+
+            throw lastException;
+        }
+
+        protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, Dictionary<string, string> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer =null)
+        {
+            var request = new Utils.Clients.WebRequest()
+            {
+                Url = url,
+                Type = RequestType.POST,
+                Cookies = cookies,
+                Referer = referer,
+                PostData = data
+            };
+            var response = await webclient.GetString(request);
+            var firstCallCookies = response.Cookies;
+
+            if (response.IsRedirect)
+            {
+                await FollowIfRedirect(response, request.Url, null, response.Cookies);
+            }
+
+            if (returnCookiesFromFirstCall)
+            {
+                response.Cookies = firstCallCookies;
+            }
+
+            return response;
+        }
+
+        protected async Task ConfigureIfOK(string cookies, bool isLoggedin, Func<Task> onError)
+        {
+            if (isLoggedin)
+            {
+                cookieHeader = cookies;
+                SaveCookieHeaderAndConfigure();
+            }
+            else
+            {
+                await onError();
+            }
+        }
+
+        public virtual IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> results)
+        {
+            foreach(var result in results)
+            {
+               if(query.Categories.Length == 0 || query.Categories.Contains(result.Category) || result.Category == 0 || TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category))
+                {
+                    yield return result;
+                }
+            }
+        }
+
+        protected void AddCategoryMapping(string trackerCategory, int newznabCategory)
+        {
+            categoryMapping.Add(new CategoryMapping(trackerCategory, newznabCategory));
+        }
+
+        protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory)
+        {
+            categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
+            if (!TorznabCaps.Categories.Contains(newznabCategory))
+                TorznabCaps.Categories.Add(newznabCategory);
+        }
+
+        protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory)
+        {
+            categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory.ID));
+            if (!TorznabCaps.Categories.Contains(newznabCategory))
+                TorznabCaps.Categories.Add(newznabCategory);
+        }
+
+        protected void AddCategoryMapping(int trackerCategory, int newznabCategory)
+        {
+            categoryMapping.Add(new CategoryMapping(trackerCategory.ToString(), newznabCategory));
+        }
+
+        protected List<string> MapTorznabCapsToTrackers(TorznabQuery query)
+        {
+            var result = new List<string>();
+            foreach (var cat in query.Categories)
+            {
+                foreach (var mapping in categoryMapping.Where(c => c.NewzNabCategory == cat))
+                {
+                    result.Add(mapping.TrackerCategory);
+                }
+            }
+
+            return result;
+        }
     }
 }
diff --git a/src/Jackett/Indexers/BeyondHD.cs b/src/Jackett/Indexers/BeyondHD.cs
index 152a9b9f950863884662ae4c96c08d0b1756b80f..cab8600b939d6fe9c5fe4f6710342c506fb38bcf 100644
--- a/src/Jackett/Indexers/BeyondHD.cs
+++ b/src/Jackett/Indexers/BeyondHD.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -17,82 +18,55 @@ namespace Jackett.Indexers
 {
     public class BeyondHD : BaseIndexer, IIndexer
     {
-        private readonly string SearchUrl = "";
-        private readonly string DownloadUrl = "";
+        private string SearchUrl { get { return SiteLink + "browse.php?c40=1&c44=1&c48=1&c89=1&c46=1&c45=1&searchin=title&incldead=0&search={0}"; } }
+        private string DownloadUrl { get { return SiteLink + "download.php?torrent={0}"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public BeyondHD(IIndexerManagerService i, Logger l)
+        public BeyondHD(IIndexerManagerService i, Logger l, IWebClient w)
             : base(name: "BeyondHD",
                 description: "Without BeyondHD, your HDTV is just a TV",
-                link: new Uri("https://beyondhd.me"),
+                link: "https://beyondhd.me/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-            SearchUrl = SiteLink + "browse.php?c40=1&c44=1&c48=1&c89=1&c46=1&c45=1&searchin=title&incldead=0&search={0}";
-            DownloadUrl = SiteLink + "download.php?torrent={0}";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataCookie();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataCookie());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
             var config = new ConfigurationDataCookie();
             config.LoadValuesFromJson(configJson);
+            cookieHeader = config.CookieHeader;
 
-            var jsonCookie = new JObject();
-            jsonCookie["cookie_header"] = config.CookieHeader;
-            cookies.FillFromJson(SiteLink, jsonCookie, logger);
-
-            var responseContent = await client.GetStringAsync(SiteLink);
+            var response = await webclient.GetString(new Utils.Clients.WebRequest()
+            {
+                Url = SiteLink,
+                Cookies = cookieHeader
+            });
 
-            if (!responseContent.Contains("logout.php"))
+            await ConfigureIfOK(cookieHeader, response.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 throw new ExceptionWithConfigData("Invalid cookie header", (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
+            });
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             List<ReleaseInfo> releases = new List<ReleaseInfo>();
 
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
-
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
+            await FollowIfRedirect(results);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 var rows = dom["table.torrenttable > tbody > tr.browse_color"];
                 foreach (var row in rows)
                 {
@@ -128,15 +102,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/BitHdtv.cs b/src/Jackett/Indexers/BitHdtv.cs
index 66ea5b035f2ed3a2a644d64b153ff6e376d59b35..1dd1e165fc0597cbe37c678011edd032e9237993 100644
--- a/src/Jackett/Indexers/BitHdtv.cs
+++ b/src/Jackett/Indexers/BitHdtv.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,91 +19,57 @@ namespace Jackett.Indexers
 {
     public class BitHdtv : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private readonly string DownloadUrl = "";
+        private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+        private string SearchUrl { get { return SiteLink + "torrents.php?cat=0&search="; } }
+        private string DownloadUrl { get { return SiteLink + "download.php?/{0}/dl.torrent"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public BitHdtv(IIndexerManagerService i, Logger l)
+        public BitHdtv(IIndexerManagerService i, Logger l, IWebClient w)
             : base(name: "BIT-HDTV",
                 description: "Home of high definition invites",
-                link: new Uri("https://www.bit-hdtv.com"),
+                link: "https://www.bit-hdtv.com/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-            LoginUrl = SiteLink + "takelogin.php";
-            SearchUrl = SiteLink + "torrents.php?cat=0&search=";
-            DownloadUrl = SiteLink + "download.php?/{0}/dl.torrent";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
 
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value }
+				{ "username", incomingConfig.Username.Value },
+				{ "password", incomingConfig.Password.Value }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("logout.php"))
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink);
+            await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 var messageEl = dom["table.detail td.text"].Last();
                 messageEl.Children("a").Remove();
                 messageEl.Children("style").Remove();
                 var errorMessage = messageEl.Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 dom["#needseed"].Remove();
                 var rows = dom["table[width='750'] > tbody"].Children();
                 foreach (var row in rows.Skip(1))
@@ -136,15 +103,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/BitMeTV.cs b/src/Jackett/Indexers/BitMeTV.cs
index 4ee4e8aad91e306e003386351d8669348de72bf7..e8b7cc123d02e3e3777089c5311c3f58ee717f63 100644
--- a/src/Jackett/Indexers/BitMeTV.cs
+++ b/src/Jackett/Indexers/BitMeTV.cs
@@ -1,7 +1,9 @@
 using CsQuery;
 using Jackett.Models;
+using Jackett.Models.IndexerConfig;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -19,68 +21,33 @@ namespace Jackett.Indexers
 {
     public class BitMeTV : BaseIndexer, IIndexer
     {
-        class BmtvConfig : ConfigurationData
-        {
-            public StringItem Username { get; private set; }
-
-            public StringItem Password { get; private set; }
-
-            public ImageItem CaptchaImage { get; private set; }
-
-            public StringItem CaptchaText { get; private set; }
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
+        private string LoginPost { get { return SiteLink + "takelogin.php"; } }
+        private string CaptchaUrl { get { return SiteLink + "visual.php"; } }
+        private string SearchUrl { get { return SiteLink + "browse.php"; } }
 
-            public BmtvConfig()
-            {
-                Username = new StringItem { Name = "Username" };
-                Password = new StringItem { Name = "Password" };
-                CaptchaImage = new ImageItem { Name = "Captcha Image" };
-                CaptchaText = new StringItem { Name = "Captcha Text" };
-            }
-
-            public override Item[] GetItems()
-            {
-                return new Item[] { Username, Password, CaptchaImage, CaptchaText };
-            }
-        }
-
-        private readonly string LoginUrl = "";
-        private readonly string LoginPost = "";
-        private readonly string CaptchaUrl = "";
-        private readonly string SearchUrl = "";
-
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public BitMeTV(IIndexerManagerService i, Logger l)
+        public BitMeTV(IIndexerManagerService i, Logger l, IWebClient c)
             : base(name: "BitMeTV",
                 description: "TV Episode specialty tracker",
-                link: new Uri("http://www.bitmetv.org"),
+                link: "http://www.bitmetv.org/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: c,
                 logger: l)
         {
-            LoginUrl = SiteLink + "login.php";
-            LoginPost = SiteLink + "takelogin.php";
-            CaptchaUrl = SiteLink + "visual.php";
-            SearchUrl = SiteLink + "browse.php";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public async Task<ConfigurationData> GetConfigurationForSetup()
         {
-            await client.GetAsync(LoginUrl);
-            var captchaImage = await client.GetByteArrayAsync(CaptchaUrl);
+            var response = await webclient.GetString(new Utils.Clients.WebRequest()
+            {
+                Url = LoginUrl
+            });
+            cookieHeader = response.Cookies;
+            var captchaImage = await RequestBytesWithCookies(CaptchaUrl);
             var config = new BmtvConfig();
-            config.CaptchaImage.Value = captchaImage;
+            config.CaptchaImage.Value = captchaImage.Content;
+            config.CaptchaCookie.Value = captchaImage.Cookies;
             return (ConfigurationData)config;
         }
 
@@ -95,46 +62,29 @@ namespace Jackett.Indexers
 				{ "secimage", config.CaptchaText.Value }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginPost, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("/logout.php"))
+            var response = await RequestLoginAndFollowRedirect(LoginPost, pairs, config.CaptchaCookie.Value, true);
+            await ConfigureIfOK(response.Cookies, response.Content.Contains("/logout.php"), async () =>
             {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 var messageEl = dom["table tr > td.embedded > h2"].Last();
                 var errorMessage = messageEl.Text();
-                var captchaImage = await client.GetByteArrayAsync(CaptchaUrl);
-                config.CaptchaImage.Value = captchaImage;
+                var captchaImage = await RequestBytesWithCookies(CaptchaUrl);
+                config.CaptchaImage.Value = captchaImage.Content;
                 config.CaptchaText.Value = "";
+                config.CaptchaCookie.Value = captchaImage.Cookies;
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format("{0}?search={1}&cat=0", SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
 
                 var table = dom["tbody > tr > .latest"].Parent().Parent();
 
@@ -177,17 +127,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-
+            return releases;
         }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
-        }
-
     }
 }
diff --git a/src/Jackett/Indexers/FrenchTorrentDb.cs b/src/Jackett/Indexers/FrenchTorrentDb.cs
index 982fabd30de444a4a0e7b2216791f4a0e145f0d4..bfd7854d29ac31ca20de773dbf06f2ed818e8bf7 100644
--- a/src/Jackett/Indexers/FrenchTorrentDb.cs
+++ b/src/Jackett/Indexers/FrenchTorrentDb.cs
@@ -1,7 +1,9 @@
 using CsQuery;
 using Jackett.Models;
+using Jackett.Models.IndexerConfig;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -16,111 +18,53 @@ namespace Jackett.Indexers
 {
     class FrenchTorrentDb : BaseIndexer, IIndexer
     {
-        public event Action<IIndexer, Newtonsoft.Json.Linq.JToken> OnSaveConfigurationRequested;
+        private string MainUrl { get { return SiteLink + "?section=INDEX"; } }
+        private string SearchUrl { get { return SiteLink + "?section=TORRENTS&exact=1&name={0}&submit=GO"; } }
 
-        public event Action<IIndexer, string, Exception> OnResultParsingError;
-
-        class ConfigurationDataBasicLoginFrenchTorrentDb : ConfigurationData
-        {
-            public StringItem Cookie { get; private set; }
-
-            public ConfigurationDataBasicLoginFrenchTorrentDb()
-            {
-                Cookie = new StringItem { Name = "Cookie" };
-            }
-
-            public override Item[] GetItems()
-            {
-                return new Item[] { Cookie };
-            }
-        }
-
-
-        private readonly string MainUrl = "";
-        private readonly string SearchUrl = "";
-
-        string cookie = string.Empty;
-
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public FrenchTorrentDb(IIndexerManagerService i, Logger l)
+        public FrenchTorrentDb(IIndexerManagerService i, Logger l, IWebClient c)
             : base(name: "FrenchTorrentDb",
                 description: "One the biggest French Torrent Tracker",
-                link: new Uri("http://www.frenchtorrentdb.com/"),
+                link: "http://www.frenchtorrentdb.com/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: c,
                 logger: l)
         {
-            MainUrl = SiteLink + "?section=INDEX";
-            SearchUrl = SiteLink + "?section=TORRENTS&exact=1&name={0}&submit=GO";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
-            client.DefaultRequestHeaders.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataUrl(SiteLink);
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink));
         }
 
         public async Task ApplyConfiguration(Newtonsoft.Json.Linq.JToken configJson)
         {
             var config = new ConfigurationDataBasicLoginFrenchTorrentDb();
             config.LoadValuesFromJson(configJson);
-            cookies.SetCookies(SiteLink, "WebsiteID=" + config.Cookie.Value);
-            var mainPage = await client.GetAsync(MainUrl);
-            string responseContent = await mainPage.Content.ReadAsStringAsync();
+            var cookies = "WebsiteID=" + config.Cookie.Value;
+            var response = await webclient.GetString(new Utils.Clients.WebRequest()
+            {
+                Url = MainUrl,
+                Type = RequestType.GET,
+                Cookies = cookies
+            });
 
-            if (!responseContent.Contains("/?section=LOGOUT"))
+            await ConfigureIfOK(cookies, response.Content.Contains("/?section=LOGOUT"), () =>
             {
                 throw new ExceptionWithConfigData("Failed to login", (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookie"] = config.Cookie.Value;
-
-                if (OnSaveConfigurationRequested != null)
-                    OnSaveConfigurationRequested(this, configSaveData);
-
-                IsConfigured = true;
-            }
+            });
         }
 
-        public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
-        {
-            cookie = (string)jsonConfig["cookie"];
-            cookies.SetCookies(SiteLink, "WebsiteID=" + cookie);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             List<ReleaseInfo> releases = new List<ReleaseInfo>();
 
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Get;
-            message.RequestUri = new Uri(episodeSearchUrl);
-
-            var response = await client.SendAsync(message);
-            var results = await response.Content.ReadAsStringAsync();
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-
-                CQ dom = results;
+                CQ dom = response.Cookies;
                 var rows = dom[".results_index ul"];
                 foreach (var row in rows)
                 {
@@ -147,16 +91,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnResultParsingError(this, results, ex);
-                throw ex;
+                OnParseError(response.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/Freshon.cs b/src/Jackett/Indexers/Freshon.cs
index cc59ab6a4d8b7699677af9e393ad1b137146b1c3..169a7d7e3bae6f9a281443a1e701f0e76b6642c2 100644
--- a/src/Jackett/Indexers/Freshon.cs
+++ b/src/Jackett/Indexers/Freshon.cs
@@ -3,6 +3,7 @@ using Jackett.Indexers;
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -21,100 +22,51 @@ namespace Jackett.Indexers
 {
     public class Freshon : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string LoginPostUrl = "";
-        private readonly string SearchUrl = "";
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
+        private string LoginPostUrl { get { return SiteLink + "login.php?action=makelogin"; } }
+        private string SearchUrl { get { return SiteLink + "browse.php"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public Freshon(IIndexerManagerService i, Logger l)
+        public Freshon(IIndexerManagerService i, Logger l, IWebClient c)
             : base(name: "FreshOnTV",
                 description: "Our goal is to provide the latest stuff in the TV show domain",
-                link: new Uri("https://freshon.tv"),
+                link: "https://freshon.tv/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: c,
                 logger: l)
         {
-
-            LoginUrl = SiteLink + "login.php";
-            LoginPostUrl = SiteLink + "login.php?action=makelogin";
-            SearchUrl = SiteLink + "browse.php";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public async Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var request = CreateHttpRequest(new Uri(LoginUrl));
-            var response = await client.SendAsync(request);
-            await response.Content.ReadAsStreamAsync();
-            var config = new ConfigurationDataBasicLogin();
-            return config;
+            return await Task.FromResult< ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value }
+				{ "username", incomingConfig.Username.Value },
+				{ "password", incomingConfig.Password.Value }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-            var message = CreateHttpRequest(new Uri(LoginPostUrl));
-            message.Method = HttpMethod.Post;
-            message.Content = content;
-            message.Headers.Referrer = new Uri(LoginUrl);
+            // Get inital cookies
+            cookieHeader = string.Empty;
+            var response = await RequestLoginAndFollowRedirect(LoginPostUrl, pairs, cookieHeader, true, null, LoginUrl);
 
-            var response = await client.SendAsync(message);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("/logout.php"))
+            await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 var messageEl = dom[".error_text"];
                 var errorMessage = messageEl.Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        HttpRequestMessage CreateHttpRequest(Uri uri)
-        {
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Get;
-            message.RequestUri = uri;
-            message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
-            return message;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             string episodeSearchUrl;
 
             if (string.IsNullOrEmpty(query.SanitizedSearchTerm))
@@ -125,12 +77,10 @@ namespace Jackett.Indexers
                 episodeSearchUrl = string.Format("{0}?search={1}&cat=0", SearchUrl, HttpUtility.UrlEncode(searchString));
             }
 
-            var request = CreateHttpRequest(new Uri(episodeSearchUrl));
-            var response = await client.SendAsync(request);
-            var results = await response.Content.ReadAsStringAsync();
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
 
                 var rows = dom["#highlight > tbody > tr"];
 
@@ -170,18 +120,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var request = CreateHttpRequest(link);
-            var response = await client.SendAsync(request);
-            var bytes = await response.Content.ReadAsByteArrayAsync();
-            return bytes;
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/HDSpace.cs b/src/Jackett/Indexers/HDSpace.cs
index 53d9ef57e7a406e5ead7b4445da2653b2760a7bf..d266a079b0c22dbfda9731067e3459aad62116a1 100644
--- a/src/Jackett/Indexers/HDSpace.cs
+++ b/src/Jackett/Indexers/HDSpace.cs
@@ -18,30 +18,23 @@ namespace Jackett.Indexers
 {
     public class HDSpace : BaseIndexer, IIndexer
     {
-
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private string cookieHeader = "";
-
-        private IWebClient webclient;
+        private string LoginUrl { get { return SiteLink + "index.php?page=login"; } }
+        private string SearchUrl { get { return SiteLink + "index.php?page=torrents&active=0&options=0&category=21%3B22&search={0}"; } }
 
         public HDSpace(IIndexerManagerService i, IWebClient wc, Logger l)
             : base(name: "HD-Space",
                 description: "Sharing The Universe",
-                link: new Uri("https://hd-space.org"),
+                link: "https://hd-space.org/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            LoginUrl = SiteLink + "index.php?page=login";
-            SearchUrl = SiteLink + "index.php?page=torrents&active=0&options=0&category=21%3B22&search={0}";
-            webclient = wc;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -49,11 +42,7 @@ namespace Jackett.Indexers
             var config = new ConfigurationDataBasicLogin();
             config.LoadValuesFromJson(configJson);
 
-            var loginPage = await webclient.GetString(new WebRequest()
-            {
-                Url = LoginUrl,
-                Type = RequestType.GET
-            });
+            var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
 
             var pairs = new Dictionary<string, string> {
                 { "uid", config.Username.Value },
@@ -61,76 +50,24 @@ namespace Jackett.Indexers
             };
 
             // Send Post
-            var loginPost = await webclient.GetString(new WebRequest()
-            {
-                Url = LoginUrl,
-                PostData = pairs,
-                Referer = LoginUrl,
-                Type = RequestType.POST,
-                Cookies = loginPage.Cookies
-            });
+            var response = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl);
 
-            if (loginPost.Status == System.Net.HttpStatusCode.OK)
+            await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("logout.php"), () =>
             {
                 var errorStr = "You have {0} remaining login attempts";
                 var remainingAttemptSpan = new Regex(string.Format(errorStr, "(.*?)")).Match(loginPage.Content).Groups[1].ToString();
                 var attempts = Regex.Replace(remainingAttemptSpan, "<.*?>", String.Empty);
                 var errorMessage = string.Format(errorStr, attempts);
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-
-            // Get result from redirect
-            var loginResult = await webclient.GetString(new WebRequest()
-            {
-                Url = SiteLink + loginPost.RedirectingTo,
-                Type = RequestType.GET,
-                Cookies = loginPost.Cookies
-            });
-
-            if (!loginResult.Content.Contains("logout.php"))
-            {
-                throw new ExceptionWithConfigData("Login failed", (ConfigurationData)config);
-            }
-            else
-            {
-                cookieHeader = loginPost.Cookies;
-                var configSaveData = new JObject();
-                configSaveData["cookies"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookies"];
-            IsConfigured = true;
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
             });
-            return response.Content;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-
-            var response = await webclient.GetString(new WebRequest()
-            {
-                Url = episodeSearchUrl,
-                Referer = SiteLink.ToString(),
-                Cookies = cookieHeader
-            });
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             var results = response.Content;
 
             try
@@ -179,7 +116,7 @@ namespace Jackett.Indexers
             {
                 OnParseError(results, ex);
             }
-            return releases.ToArray();
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/HDTorrents.cs b/src/Jackett/Indexers/HDTorrents.cs
index 9d8edc90dd5f9581ee2c070b080a5ff188c6446d..d8e3d5158ca854e7fd81e2394f68e1f99c232798 100644
--- a/src/Jackett/Indexers/HDTorrents.cs
+++ b/src/Jackett/Indexers/HDTorrents.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,185 +19,119 @@ namespace Jackett.Indexers
 {
     public class HDTorrents : BaseIndexer, IIndexer
     {
-        private readonly string SearchUrl = "";
-        private static string LoginUrl = "";
+        private string SearchUrl { get { return SiteLink + "torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page=0"; } }
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
         private const int MAXPAGES = 3;
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public HDTorrents(IIndexerManagerService i, Logger l)
+        public HDTorrents(IIndexerManagerService i, Logger l, IWebClient w)
             : base(name: "HD-Torrents",
                 description: "HD-Torrents is a private torrent website with HD torrents and strict rules on their content.",
-                link: new Uri("http://hdts.ru"),// Of the accessible domains the .ru seems the most reliable.  https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me
+                link: "http://hdts.ru/",// Of the accessible domains the .ru seems the most reliable.  https://hdts.ru | https://hd-torrents.org | https://hd-torrents.net | https://hd-torrents.me
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-            SearchUrl = SiteLink + "torrents.php?search={0}&active=1&options=0&category%5B%5D=59&category%5B%5D=60&category%5B%5D=30&category%5B%5D=38&page={1}";
-            LoginUrl = SiteLink + "login.php";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
-        }
-
-        HttpRequestMessage CreateHttpRequest(string url)
-        {
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Get;
-            message.RequestUri = new Uri(url);
-            message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
-            return message;
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
-            var startMessage = CreateHttpRequest(LoginUrl);
-            var results = await (await client.SendAsync(startMessage)).Content.ReadAsStringAsync();
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
+            var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
 
             var pairs = new Dictionary<string, string> {
-				{ "uid", config.Username.Value },
-				{ "pwd", config.Password.Value }
-			};
-
-            var content = new FormUrlEncodedContent(pairs);
-
-            var loginRequest = CreateHttpRequest(LoginUrl);
-            loginRequest.Method = HttpMethod.Post;
-            loginRequest.Content = content;
-            loginRequest.Headers.Referrer = new Uri("https://hd-torrents.org/torrents.php");
+                { "uid", incomingConfig.Username.Value },
+                { "pwd", incomingConfig.Password.Value }
+            };
 
-            var response = await client.SendAsync(loginRequest);
-            var responseContent = await response.Content.ReadAsStringAsync();
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl);
 
-            if (!responseContent.Contains("If your browser doesn't have javascript enabled"))
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("If your browser doesn't have javascript enabled"), () =>
             {
                 var errorMessage = "Couldn't login";
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, Uri baseUrl)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-            List<string> searchurls = new List<string>();
-
+            var releases = new List<ReleaseInfo>();
+            var searchurls = new List<string>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
-            for (int page = 0; page < MAXPAGES; page++)
+            var searchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
+            var results = await RequestStringWithCookiesAndRetry(searchUrl);
+            try
             {
-                searchurls.Add(string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()), page));
-            }
+                CQ dom = results.Content;
+                ReleaseInfo release;
 
-            foreach (string SearchUrl in searchurls)
-            {
-                var results = await client.GetStringAsync(SearchUrl);
-                try
+                int rowCount = 0;
+                var rows = dom[".mainblockcontenttt > tbody > tr"];
+                foreach (var row in rows)
                 {
-                    CQ dom = results;
-                    ReleaseInfo release;
-
-                    int rowCount = 0;
-                    var rows = dom[".mainblockcontenttt > tbody > tr"];
-                    foreach (var row in rows)
+                    CQ qRow = row.Cq();
+                    if (rowCount < 2 || qRow.Children().Count() != 12) //skip 2 rows because there's an empty row & a title/sort row
                     {
-                        CQ qRow = row.Cq();
-                        if (rowCount < 2 || qRow.Children().Count() != 12) //skip 2 rows because there's an empty row & a title/sort row
-                        {
-                            rowCount++;
-                            continue;
-                        }
+                        rowCount++;
+                        continue;
+                    }
 
-                        release = new ReleaseInfo();
+                    release = new ReleaseInfo();
 
-                        release.Title = qRow.Find("td.mainblockcontent b a").Text();
-                        release.Description = release.Title;
+                    release.Title = qRow.Find("td.mainblockcontent b a").Text();
+                    release.Description = release.Title;
 
-                        if (0 != qRow.Find("td.mainblockcontent u").Length)
+                    if (0 != qRow.Find("td.mainblockcontent u").Length)
+                    {
+                        var imdbStr = qRow.Find("td.mainblockcontent u").Parent().First().Attr("href").Replace("http://www.imdb.com/title/tt", "").Replace("/", "");
+                        long imdb;
+                        if (ParseUtil.TryCoerceLong(imdbStr, out imdb))
                         {
-                            var imdbStr = qRow.Find("td.mainblockcontent u").Parent().First().Attr("href").Replace("http://www.imdb.com/title/tt", "").Replace("/", "");
-                            long imdb;
-                            if (ParseUtil.TryCoerceLong(imdbStr, out imdb))
-                            {
-                                release.Imdb = imdb;
-                            }
+                            release.Imdb = imdb;
                         }
+                    }
 
-                        release.MinimumRatio = 1;
-                        release.MinimumSeedTime = 172800;
+                    release.MinimumRatio = 1;
+                    release.MinimumSeedTime = 172800;
 
 
 
-                        int seeders, peers;
-                        if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(9).FirstChild.FirstChild.InnerText, out seeders))
+                    int seeders, peers;
+                    if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(9).FirstChild.FirstChild.InnerText, out seeders))
+                    {
+                        release.Seeders = seeders;
+                        if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(10).FirstChild.FirstChild.InnerText, out peers))
                         {
-                            release.Seeders = seeders;
-                            if (ParseUtil.TryCoerceInt(qRow.Find("td").Get(10).FirstChild.FirstChild.InnerText, out peers))
-                            {
-                                release.Peers = peers + release.Seeders;
-                            }
+                            release.Peers = peers + release.Seeders;
                         }
+                    }
 
-                        string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText;
-                        release.Size = ReleaseInfo.GetBytes(fullSize);
+                    string fullSize = qRow.Find("td.mainblockcontent").Get(6).InnerText;
+                    release.Size = ReleaseInfo.GetBytes(fullSize);
 
-                        release.Guid = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href"));
-                        release.Link = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href"));
-                        release.Comments = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments");
+                    release.Guid = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href"));
+                    release.Link = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent").Get(3).FirstChild.GetAttribute("href"));
+                    release.Comments = new Uri(SiteLink + "/" + qRow.Find("td.mainblockcontent b a").Attr("href") + "#comments");
 
-                        string[] dateSplit = qRow.Find("td.mainblockcontent").Get(5).InnerHTML.Split(',');
-                        string dateString = dateSplit[1].Substring(0, dateSplit[1].IndexOf('>'));
-                        release.PublishDate = DateTime.Parse(dateString, CultureInfo.InvariantCulture);
+                    string[] dateSplit = qRow.Find("td.mainblockcontent").Get(5).InnerHTML.Split(',');
+                    string dateString = dateSplit[1].Substring(0, dateSplit[1].IndexOf('>'));
+                    release.PublishDate = DateTime.Parse(dateString, CultureInfo.InvariantCulture);
 
-                        releases.Add(release);
-                    }
-                }
-                catch (Exception ex)
-                {
-                    OnParseError(results, ex);
+                    releases.Add(release);
                 }
             }
+            catch (Exception ex)
+            {
+                OnParseError(results.Content, ex);
+            }
 
-            return releases.ToArray();
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            return await PerformQuery(query, SiteLink);
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/IIndexer.cs b/src/Jackett/Indexers/IIndexer.cs
index cf6ba4c749e3d16ab2eeff55e56ea85c09314c20..c768591eb9bfbdfddff7d1660de23d88734d55f8 100644
--- a/src/Jackett/Indexers/IIndexer.cs
+++ b/src/Jackett/Indexers/IIndexer.cs
@@ -11,12 +11,12 @@ namespace Jackett.Indexers
 {
     public interface IIndexer
     {
+        string SiteLink { get; }
+
         string DisplayName { get; }
         string DisplayDescription { get; }
         string ID { get; }
 
-        Uri SiteLink { get; }
-
         TorznabCapabilities TorznabCaps { get; }
 
         // Whether this indexer has been configured, verified and saved in the past and has the settings required for functioning
@@ -31,7 +31,9 @@ namespace Jackett.Indexers
         // Called on startup when initializing indexers from saved configuration
         void LoadFromSavedConfiguration(JToken jsonConfig);
 
-        Task<ReleaseInfo[]> PerformQuery(TorznabQuery query);
+        Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query);
+
+        IEnumerable<ReleaseInfo> FilterResults(TorznabQuery query, IEnumerable<ReleaseInfo> input);
 
         Task<byte[]> Download(Uri link);
     }
diff --git a/src/Jackett/Indexers/IPTorrents.cs b/src/Jackett/Indexers/IPTorrents.cs
index f9b7a2de20e26d18bb11491c4a71e24338a07e01..975c7a74e53d49bc1f65a06c21a4386ef979930f 100644
--- a/src/Jackett/Indexers/IPTorrents.cs
+++ b/src/Jackett/Indexers/IPTorrents.cs
@@ -7,6 +7,7 @@ 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;
@@ -19,111 +20,118 @@ namespace Jackett.Indexers
 {
     public class IPTorrents : BaseIndexer, IIndexer
     {
-        private readonly string SearchUrl = "";
-        private string cookieHeader = "";
-
-        private IWebClient webclient;
+        private string BrowseUrl { get { return SiteLink + "t"; } }
 
         public IPTorrents(IIndexerManagerService i, IWebClient wc, Logger l)
             : base(name: "IPTorrents",
                 description: "Always a step ahead.",
-                link: new Uri("https://iptorrents.com/"),
+                link: "https://iptorrents.com/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            TorznabCaps.Categories.Add(new TorznabCategory { ID = "5070", Name = "TV/Anime" });
-            SearchUrl = SiteLink + "t?26=&55=&78=&23=&24=&25=&66=&82=&65=&83=&79=&22=&5=&4=&60=&q="; 
-            webclient = wc;
+            AddCategoryMapping(72, TorznabCatType.Movies);
+            AddCategoryMapping(77, TorznabCatType.MoviesSD);
+            AddCategoryMapping(89, TorznabCatType.MoviesSD);
+            AddCategoryMapping(90, TorznabCatType.MoviesSD);
+            AddCategoryMapping(96, TorznabCatType.MoviesSD);
+            AddCategoryMapping(6, TorznabCatType.MoviesSD);
+            AddCategoryMapping(48, TorznabCatType.MoviesHD);
+            AddCategoryMapping(54, TorznabCatType.Movies);
+            AddCategoryMapping(62, TorznabCatType.MoviesSD);
+            AddCategoryMapping(38, TorznabCatType.MoviesForeign);
+            AddCategoryMapping(68, TorznabCatType.Movies);
+            AddCategoryMapping(20, TorznabCatType.MoviesHD);
+            AddCategoryMapping(7, TorznabCatType.MoviesSD);
+
+            AddCategoryMapping(73, TorznabCatType.TV);
+            AddCategoryMapping(26, TorznabCatType.TVSD);
+            AddCategoryMapping(55, TorznabCatType.TVSD);
+            AddCategoryMapping(78, TorznabCatType.TVSD);
+            AddCategoryMapping(23, TorznabCatType.TVHD);
+            AddCategoryMapping(24, TorznabCatType.TVSD);
+            AddCategoryMapping(25, TorznabCatType.TVSD);
+            AddCategoryMapping(66, TorznabCatType.TVSD);
+            AddCategoryMapping(82, TorznabCatType.TVSD);
+            AddCategoryMapping(65, TorznabCatType.TV);
+            AddCategoryMapping(83, TorznabCatType.TV);
+            AddCategoryMapping(79, TorznabCatType.TVSD);
+            AddCategoryMapping(22, TorznabCatType.TVHD);
+            AddCategoryMapping(79, TorznabCatType.TVSD);
+            AddCategoryMapping(4, TorznabCatType.TVSD);
+            AddCategoryMapping(5, TorznabCatType.TVHD);
+
+            AddCategoryMapping(75, TorznabCatType.Audio);
+            AddCategoryMapping(73, TorznabCatType.Audio);
+            AddCategoryMapping(80, TorznabCatType.AudioLossless);
+            AddCategoryMapping(93, TorznabCatType.Audio);
+
+            AddCategoryMapping(60, TorznabCatType.Anime);
+            AddCategoryMapping(1, TorznabCatType.Apps);
+            AddCategoryMapping(64, TorznabCatType.AudioBooks);
+            AddCategoryMapping(35, TorznabCatType.Books);
+            AddCategoryMapping(94, TorznabCatType.Comic);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>((ConfigurationData)config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value }
-			};
-
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
+                { "username", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value }
+            };
+            var request = new Utils.Clients.WebRequest()
             {
-                Url = SiteLink.ToString(),
-                PostData = pairs,
-                Referer = SiteLink.ToString(),
-                Type = RequestType.POST
-            });
-
-            cookieHeader = response.Cookies;
-            if (response.Status == HttpStatusCode.Found)
+                Url = SiteLink,
+                Type = RequestType.POST,
+                Referer = SiteLink,
+                PostData = pairs
+            };
+            var response = await webclient.GetString(request);
+            var firstCallCookies = response.Cookies;
+            // Redirect to ? then to /t
+            await FollowIfRedirect(response, request.Url, null, firstCallCookies);
+
+            await ConfigureIfOK(firstCallCookies, response.Content.Contains("/my.php"), () =>
             {
-                response = await webclient.GetString(new Utils.Clients.WebRequest()
-                {
-                    Url = SearchUrl,
-                    Referer = SiteLink.ToString(),
-                    Cookies = response.Cookies
-                });
-            }
-
-            var responseContent = response.Content;
-
-            if (!responseContent.Contains("/my.php"))
-            {
-                CQ dom = responseContent;
+                CQ dom = response.Content;
                 var messageEl = dom["body > div"].First();
                 var errorMessage = messageEl.Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookie_header"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookie_header"];
-            IsConfigured = true;
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
-            var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
+            var searchUrl = BrowseUrl;
+            var trackerCats = MapTorznabCapsToTrackers(query);
+            var queryCollection = new NameValueCollection();
 
-            WebClientStringResult response = null;
+            if (!string.IsNullOrWhiteSpace(searchString))
+            {
+                queryCollection.Add("q", searchString);
+            }
 
-            // Their web server is fairly flakey - try up to three times.
-            for (int i = 0; i < 3; i++)
+            foreach (var cat in MapTorznabCapsToTrackers(query))
             {
-                try
-                {
-                    response = await webclient.GetString(new Utils.Clients.WebRequest()
-                    {
-                        Url = episodeSearchUrl,
-                        Referer = SiteLink.ToString(),
-                        Cookies = cookieHeader
-                    });
+                queryCollection.Add(cat, string.Empty);
+            }
 
-                    break;
-                }
-                catch (Exception e)
-                {
-                    logger.Error("On attempt " + (i + 1) + " checking for results from IPTorrents: " + e.Message);
-                }
+            if (queryCollection.Count > 0)
+            {
+                searchUrl += "?" + queryCollection.GetQueryString();
             }
+                       
+            var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
 
             var results = response.Content;
             try
@@ -162,6 +170,9 @@ namespace Jackett.Indexers
                     release.Seeders = ParseUtil.CoerceInt(qRow.Find(".t_seeders").Text().Trim());
                     release.Peers = ParseUtil.CoerceInt(qRow.Find(".t_leechers").Text().Trim()) + release.Seeders;
 
+                    var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(1);
+                    release.Category = MapTrackerCatToNewznab(cat);
+
                     releases.Add(release);
                 }
             }
@@ -170,18 +181,7 @@ namespace Jackett.Indexers
                 OnParseError(results, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
-            });
-
-            return response.Content;
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/ImmortalSeed.cs b/src/Jackett/Indexers/ImmortalSeed.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7bd11569ea4df46b1b9f837b467a174146fdb5dd
--- /dev/null
+++ b/src/Jackett/Indexers/ImmortalSeed.cs
@@ -0,0 +1,158 @@
+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;
+
+namespace Jackett.Indexers
+{
+    public class ImmortalSeed : BaseIndexer, IIndexer
+    {
+        private string BrowsePage { get { return SiteLink + "browse.php"; } }
+        private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+        private string QueryString { get { return "?do=search&keywords={0}&search_type=t_name&category=0&include_dead_torrents=no"; } }
+
+        public ImmortalSeed(IIndexerManagerService i, IWebClient wc, Logger l)
+            : base(name: "ImmortalSeed",
+                description: "ImmortalSeed",
+                link: "http://immortalseed.me/",
+                caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                manager: i,
+                client: wc,
+                logger: l)
+        {
+            AddCategoryMapping(32, TorznabCatType.Anime);
+            AddCategoryMapping(47, TorznabCatType.TVSD);
+            AddCategoryMapping(8, TorznabCatType.TVHD);
+            AddCategoryMapping(48, TorznabCatType.TVHD);
+            AddCategoryMapping(9, TorznabCatType.TVSD);
+            AddCategoryMapping(4, TorznabCatType.TVHD);
+            AddCategoryMapping(6, TorznabCatType.TVSD);
+
+            AddCategoryMapping(22, TorznabCatType.Books);
+            AddCategoryMapping(41, TorznabCatType.Comic);
+            AddCategoryMapping(23, TorznabCatType.Apps);
+
+            AddCategoryMapping(16, TorznabCatType.MoviesHD);
+            AddCategoryMapping(17, TorznabCatType.MoviesSD);
+            AddCategoryMapping(14, TorznabCatType.MoviesSD);
+            AddCategoryMapping(34, TorznabCatType.MoviesForeign);
+            AddCategoryMapping(18, TorznabCatType.MoviesForeign);
+            AddCategoryMapping(33, TorznabCatType.MoviesForeign);
+
+            AddCategoryMapping(34, TorznabCatType.Audio);
+            AddCategoryMapping(37, TorznabCatType.AudioLossless);
+            AddCategoryMapping(35, TorznabCatType.AudioBooks);
+            AddCategoryMapping(36, TorznabCatType.AudioLossy);
+
+        }
+
+        public Task<ConfigurationData> GetConfigurationForSetup()
+        {
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
+        }
+
+        public async Task ApplyConfiguration(JToken configJson)
+        {
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
+            var pairs = new Dictionary<string, string> {
+                { "username", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value }
+            };
+            var request = new Utils.Clients.WebRequest()
+            {
+                Url = LoginUrl,
+                Type = RequestType.POST,
+                Referer = SiteLink,
+                PostData = pairs
+            };
+            var response = await webclient.GetString(request);
+            CQ splashDom = response.Content;
+            var link = splashDom[".trow2 a"].First();
+            var resultPage = await RequestStringWithCookies(link.Attr("href"), response.Cookies);
+            CQ resultDom = resultPage.Content;
+
+            await ConfigureIfOK(response.Cookies, resultPage.Content.Contains("/logout.php"), () =>
+            {
+                var tries = resultDom["#main tr:eq(1) td font"].First().Text();
+                var errorMessage = "Incorrect username or password! " + tries + " tries remaining.";
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
+        }
+
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
+        {
+            var releases = new List<ReleaseInfo>();
+            var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
+            var searchUrl = BrowsePage;
+
+            if (!string.IsNullOrWhiteSpace(searchString))
+            {
+                searchUrl += string.Format(QueryString, HttpUtility.UrlEncode(searchString));
+            }
+
+            var results = await RequestStringWithCookiesAndRetry(searchUrl);
+
+            try
+            {
+                CQ dom = results.Content;
+
+                var rows = dom["#sortabletable tr"];
+                foreach (var row in rows.Skip(1))
+                {
+                    var release = new ReleaseInfo();
+                    var qRow = row.Cq();
+                    release.Title = qRow.Find(".tooltip-content div").First().Text();
+                    if (string.IsNullOrWhiteSpace(release.Title))
+                        continue;
+                    release.Description = qRow.Find(".tooltip-content div").Get(1).InnerText.Trim();
+
+                    var qLink = row.Cq().Find("td:eq(2) a:eq(1)");
+                    release.Link = new Uri(qLink.Attr("href"));
+                    release.Guid = release.Link;
+                    release.Comments = new Uri(qRow.Find(".tooltip-target a").First().Attr("href"));
+
+                    // 07-22-2015 11:08 AM
+                    var dateString = qRow.Find("td:eq(1) div").Last().Children().Remove().End().Text().Trim();
+                    release.PublishDate = DateTime.ParseExact(dateString, "MM-dd-yyyy hh:mm tt", CultureInfo.InvariantCulture);
+
+                    var sizeStr = qRow.Find("td:eq(4)").Text().Trim();
+                    release.Size = ReleaseInfo.GetBytes(sizeStr);
+
+                    release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:eq(6)").Text().Trim());
+                    release.Peers = ParseUtil.CoerceInt(qRow.Find("td:eq(7)").Text().Trim()) + release.Seeders;
+
+                    var catLink = row.Cq().Find("td:eq(0) a").First().Attr("href");
+                    var catSplit = catLink.IndexOf("category=");
+                    if (catSplit > -1)
+                    {
+                        catLink = catLink.Substring(catSplit + 9);
+                    }
+
+                    release.Category = MapTrackerCatToNewznab(catLink);
+                    releases.Add(release);
+                }
+            }
+            catch (Exception ex)
+            {
+                OnParseError(results.Content, ex);
+            }
+
+            return releases;
+        }
+
+     
+    }
+}
diff --git a/src/Jackett/Indexers/MoreThanTV.cs b/src/Jackett/Indexers/MoreThanTV.cs
index 1d8c3199f9da8fc35cc2762199b7c66974644ff1..8d4d74a8cbb6b009c12f92348dc54ca38b0ad881 100644
--- a/src/Jackett/Indexers/MoreThanTV.cs
+++ b/src/Jackett/Indexers/MoreThanTV.cs
@@ -19,33 +19,25 @@ namespace Jackett.Indexers
 {
     public class MoreThanTV : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private readonly string DownloadUrl = "";
-        private readonly string GuidUrl = "";
-
-        private IWebClient client;
-        private string cookieHeader = "";
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
+        private string SearchUrl { get { return SiteLink + "ajax.php?action=browse&searchstr="; } }
+        private string DownloadUrl { get { return SiteLink + "torrents.php?action=download&id="; } }
+        private string GuidUrl { get { return SiteLink + "torrents.php?torrentid="; } }
 
         public MoreThanTV(IIndexerManagerService i, IWebClient c, Logger l)
             : base(name: "MoreThanTV",
                 description: "ROMANIAN Private Torrent Tracker for TV / MOVIES, and the internal tracker for the release group DRACULA.",
-                link: new Uri("https://www.morethan.tv"),
+                link: "https://www.morethan.tv/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: c,
                 logger: l)
         {
-            LoginUrl = SiteLink + "login.php";
-            SearchUrl = SiteLink + "ajax.php?action=browse&searchstr=";
-            DownloadUrl = SiteLink + "torrents.php?action=download&id=";
-            GuidUrl = SiteLink + "torrents.php?torrentid=";
-            client = c;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -58,45 +50,15 @@ namespace Jackett.Indexers
 				{ "login", "Log in" },
 				{ "keeplogged", "1" }
 			};
-            
-            var loginResponse = await client.GetString(new Utils.Clients.WebRequest()
-            {
-                PostData = pairs,
-                Url = LoginUrl,
-                Type = RequestType.POST
-            });
-            
-            if (loginResponse.Status == HttpStatusCode.Found)
-            {
-                cookieHeader = loginResponse.Cookies;
-                loginResponse = await client.GetString(new Utils.Clients.WebRequest()
-                {
-                    Url = SiteLink.ToString(),
-                    Cookies = cookieHeader
-                });
-            }
 
-            if (!loginResponse.Content.Contains("logout.php?"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, SearchUrl, SiteLink);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php?"), () =>
             {
-                CQ dom = loginResponse.Content;
+                CQ dom = result.Content;
                 dom["#loginform > table"].Remove();
                 var errorMessage = dom["#loginform"].Text().Trim().Replace("\n\t", " ");
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookie_header"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookie_header"];
-            IsConfigured = true;
+            });
         }
 
         private void FillReleaseInfoFromJson(ReleaseInfo release, JObject r)
@@ -110,33 +72,15 @@ namespace Jackett.Indexers
             release.Link = new Uri(DownloadUrl + id);
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = SearchUrl + HttpUtility.UrlEncode(searchString);
-            WebClientStringResult response = null; 
+            WebClientStringResult response = null;
 
-            // Their web server is fairly flakey - try up to three times.
-            for(int i = 0; i < 3; i++)
-            {
-                try
-                {
-                    response = await client.GetString(new Utils.Clients.WebRequest()
-                    {
-                        Url = episodeSearchUrl,
-                        Type = RequestType.GET,
-                        Cookies = cookieHeader
-                    });
+            response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
-                    break;
-                }
-                catch (Exception e){
-                    logger.Error("On attempt " + (i+1) + " checking for results from MoreThanTv: " + e.Message );
-                }
-            }
-            
             try
             {
                 var json = JObject.Parse(response.Content);
@@ -173,7 +117,6 @@ namespace Jackett.Indexers
                         FillReleaseInfoFromJson(release, r);
                         releases.Add(release);
                     }
-
                 }
             }
             catch (Exception ex)
@@ -181,19 +124,7 @@ namespace Jackett.Indexers
                 OnParseError(response.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var result = await client.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Cookies = cookieHeader,
-                Url = link.ToString(),
-               Type = RequestType.GET
-            });
-
-            return result.Content;
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/Pretome.cs b/src/Jackett/Indexers/Pretome.cs
index 21329864c919aba06c07132fc7b46659aea5d903..2b45755612b18c0e9f9f4b3f08ce1252563dec95 100644
--- a/src/Jackett/Indexers/Pretome.cs
+++ b/src/Jackett/Indexers/Pretome.cs
@@ -11,52 +11,30 @@ using NLog;
 using Jackett.Utils;
 using CsQuery;
 using System.Web;
+using Jackett.Models.IndexerConfig;
 
 namespace Jackett.Indexers
 {
     public class Pretome : BaseIndexer, IIndexer
     {
-
-        class PretomeConfiguration : ConfigurationDataBasicLogin
-        {
-            public StringItem Pin { get; private set; }
-
-            public PretomeConfiguration() : base()
-            {
-                Pin = new StringItem { Name = "Login Pin Number" };
-            }
-
-            public override Item[] GetItems()
-            {
-                return new Item[] { Pin, Username, Password };
-            }
-        }
-
-        private readonly string LoginUrl = "";
-        private readonly string LoginReferer = "";
-        private readonly string SearchUrl = "";
-        private string cookieHeader = "";
-
-        private IWebClient webclient;
+        private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+        private string LoginReferer { get { return SiteLink + "index.php?cat=1"; } }
+        private string SearchUrl { get { return SiteLink + "browse.php?tags=&st=1&tf=all&cat%5B%5D=7&search={0}"; } }
 
         public Pretome(IIndexerManagerService i, IWebClient wc, Logger l)
             : base(name: "PreToMe",
                 description: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows",
-                link: new Uri("https://pretome.info/"),
+                link: "https://pretome.info/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                client: wc,
                 manager: i,
                 logger: l)
         {
-            LoginUrl = SiteLink + "takelogin.php";
-            LoginReferer = SiteLink + "index.php?cat=1";
-            SearchUrl = SiteLink + "browse.php?tags=&st=1&tf=all&cat%5B%5D=7&search={0}";
-            webclient = wc;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new PretomeConfiguration();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new PretomeConfiguration());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -64,11 +42,7 @@ namespace Jackett.Indexers
             var config = new PretomeConfiguration();
             config.LoadValuesFromJson(configJson);
 
-            var loginPage = await webclient.GetString(new WebRequest()
-            {
-                Url = LoginUrl,
-                Type = RequestType.GET
-            });
+            var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
 
             var pairs = new Dictionary<string, string> {
                 { "returnto", "%2F" },
@@ -78,78 +52,34 @@ namespace Jackett.Indexers
                 { "login", "Login" }
             };
 
-
             // Send Post
-            var loginPost = await webclient.GetString(new WebRequest()
-            {
-                Url = LoginUrl,
-                PostData = pairs,
-                Referer = LoginReferer,
-                Type = RequestType.POST,
-                Cookies = loginPage.Cookies
-            });
-
-            if (loginPost.RedirectingTo == null)
+            var result = await PostDataWithCookies(LoginUrl, pairs, loginPage.Cookies);
+            if (result.RedirectingTo == null)
             {
                 throw new ExceptionWithConfigData("Login failed. Did you use the PIN number that pretome emailed you?", (ConfigurationData)config);
             }
-
+            var loginCookies = result.Cookies;
             // Get result from redirect
-            var loginResult = await webclient.GetString(new WebRequest()
-            {
-                Url = loginPost.RedirectingTo,
-                Type = RequestType.GET,
-                Cookies = loginPost.Cookies
-            });
+            await FollowIfRedirect(result,LoginUrl,null, loginCookies);
 
-            if (!loginResult.Content.Contains("logout.php"))
+            await ConfigureIfOK(loginCookies, result.Content != null && result.Content.Contains("logout.php"), () =>
             {
+                cookieHeader = string.Empty;
                 throw new ExceptionWithConfigData("Failed", (ConfigurationData)config);
-            }
-            else
-            {
-                cookieHeader = loginPost.Cookies;
-                var configSaveData = new JObject();
-                configSaveData["cookies"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookies"];
-            IsConfigured = true;
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
             });
-            return response.Content;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
 
-            var response = await webclient.GetString(new WebRequest()
-            {
-                Url = episodeSearchUrl,
-                Referer = SiteLink.ToString(),
-                Cookies = cookieHeader
-            });
-            var results = response.Content;
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
             try
             {
-                CQ dom = results;
+                CQ dom = response.Content;
                 var rows = dom["table > tbody > tr.browse"];
                 foreach (var row in rows)
                 {
@@ -186,9 +116,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(response.Content, ex);
             }
-            return releases.ToArray();
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/PrivateHD.cs b/src/Jackett/Indexers/PrivateHD.cs
index 6337b8b5026dd9b5628a51475c31f20d6277e6e9..23a79a399fef63b45c924ea60de62be03d086c55 100644
--- a/src/Jackett/Indexers/PrivateHD.cs
+++ b/src/Jackett/Indexers/PrivateHD.cs
@@ -19,121 +19,59 @@ namespace Jackett.Indexers
 {
     public class PrivateHD : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private string cookieHeader = "";
-
-        private IWebClient webclient;
+        private string LoginUrl { get { return SiteLink + "auth/login"; } }
+        private string SearchUrl { get { return SiteLink + "torrents?in=1&type=2&search={0}"; } }
 
         public PrivateHD(IIndexerManagerService i, IWebClient wc, Logger l)
             : base(name: "PrivateHD",
                 description: "BitTorrent site for High Quality, High Definition (HD) movies and TV Shows",
-                link: new Uri("https://privatehd.to"),
+                link: "https://privatehd.to/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            LoginUrl = SiteLink + "auth/login";
-            SearchUrl = SiteLink + "torrents?in=1&type=2&search={0}";
-            webclient = wc;
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
-            var loginPage = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = LoginUrl,
-                Type = RequestType.GET
-            });
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
+            var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
             var token = new Regex("Avz.CSRF_TOKEN = '(.*?)';").Match(loginPage.Content).Groups[1].ToString();
             var pairs = new Dictionary<string, string> {
                 { "_token", token },
-                { "username_email", config.Username.Value },
-                { "password", config.Password.Value },
+                { "username_email", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value },
                 { "remember", "on" }
             };
 
-            // Send Post
-            var loginPost = await webclient.GetString(new Utils.Clients.WebRequest()
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginUrl);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("auth/logout"), () =>
             {
-                Url = LoginUrl,
-                PostData = pairs,
-                Referer = LoginUrl,
-                Type = RequestType.POST,
-                Cookies = loginPage.Cookies
-            });
-
-            // Get result from redirect
-            var loginResult = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = loginPost.RedirectingTo,
-                Type = RequestType.GET,
-                Cookies = loginPost.Cookies
-            });
-
-            if (!loginResult.Content.Contains("auth/logout"))
-            {
-                CQ dom = loginResult.Content;
+                CQ dom = result.Content;
                 var messageEl = dom[".form-error"];
                 var errorMessage = messageEl.Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                cookieHeader = loginPost.Cookies;
-                var configSaveData = new JObject();
-                configSaveData["cookies"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
             });
-
-            return response.Content;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookieHeader = (string)jsonConfig["cookies"];
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
 
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Url = episodeSearchUrl,
-                Referer = SiteLink.ToString(),
-                Cookies = cookieHeader
-            });
-            var results = response.Content;
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
             try
             {
-                CQ dom = results;
+                CQ dom = response.Content;
                 var rows = dom["table > tbody > tr"];
                 foreach (var row in rows)
                 {
@@ -165,9 +103,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(response.Content, ex);
             }
-            return releases.ToArray();
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/Rarbg.cs b/src/Jackett/Indexers/Rarbg.cs
deleted file mode 100644
index 4d43927b1a02b4c63a8a6f7b7cbd927cc7ecc87b..0000000000000000000000000000000000000000
--- a/src/Jackett/Indexers/Rarbg.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using CsQuery;
-using Jackett.Models;
-using Jackett.Services;
-using Jackett.Utils;
-using Newtonsoft.Json.Linq;
-using NLog;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Jackett.Indexers
-{
-    public class Rarbg : BaseIndexer, IIndexer
-    {
-        const string DefaultUrl = "http://torrentapi.org";
-        const string TokenUrl = "/pubapi.php?get_token=get_token&format=json";
-        const string SearchTVRageUrl = "/pubapi.php?mode=search&search_tvrage={0}&token={1}&format=json&min_seeders=1";
-        const string SearchQueryUrl = "/pubapi.php?mode=search&search_string={0}&token={1}&format=json&min_seeders=1";
-        private string BaseUrl;
-
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-
-        public Rarbg(IIndexerManagerService i, Logger l)
-            : base(name: "RARBG",
-                description: "RARBG",
-                link: new Uri("https://rarbg.com"),
-                caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
-                manager: i,
-                logger: l)
-        {
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
-        }
-
-        public Task<ConfigurationData> GetConfigurationForSetup()
-        {
-            var config = new ConfigurationDataUrl(DefaultUrl);
-            return Task.FromResult<ConfigurationData>(config);
-        }
-
-        public async Task ApplyConfiguration(JToken configJson)
-        {
-            var config = new ConfigurationDataUrl(DefaultUrl);
-            config.LoadValuesFromJson(configJson);
-
-            var formattedUrl = config.GetFormattedHostUrl();
-            var token = await GetToken(formattedUrl);
-            /*var releases = await PerformQuery(new TorznabQuery(), formattedUrl);
-            if (releases.Length == 0)
-                throw new Exception("Could not find releases from this URL");*/
-
-            BaseUrl = formattedUrl;
-
-            var configSaveData = new JObject();
-            configSaveData["base_url"] = BaseUrl;
-            SaveConfig(configSaveData);
-            IsConfigured = true;
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            BaseUrl = (string)jsonConfig["base_url"];
-            IsConfigured = true;
-        }
-
-        HttpRequestMessage CreateHttpRequest(string uri)
-        {
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Get;
-            message.RequestUri = new Uri(uri);
-            message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
-            return message;
-        }
-
-        async Task<string> GetToken(string url)
-        {
-            var request = CreateHttpRequest(url + TokenUrl);
-            var response = await client.SendAsync(request);
-            var result = await response.Content.ReadAsStringAsync();
-            JObject obj = JObject.Parse(result);
-            return (string)obj["token"];
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            return await PerformQuery(query, BaseUrl);
-        }
-
-        async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
-        {
-
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
-            string token = await GetToken(baseUrl);
-            string searchUrl;
-            if (query.RageID != 0)
-                searchUrl = string.Format(baseUrl + SearchTVRageUrl, query.RageID, token);
-            else
-                searchUrl = string.Format(baseUrl + SearchQueryUrl, query.SanitizedSearchTerm, token);
-
-            var request = CreateHttpRequest(searchUrl);
-            var response = await client.SendAsync(request);
-            var results = await response.Content.ReadAsStringAsync();
-            try
-            {
-                var jItems = JArray.Parse(results);
-                foreach (JObject item in jItems)
-                {
-                    var release = new ReleaseInfo();
-                    release.Title = (string)item["f"];
-                    release.MagnetUri = new Uri((string)item["d"]);
-                    release.Guid = release.MagnetUri;
-                    release.PublishDate = new DateTime(1970, 1, 1);
-                    release.Size = 0;
-                    release.Seeders = 1;
-                    release.Peers = 1;
-                    release.MinimumRatio = 1;
-                    release.MinimumSeedTime = 172800;
-                    releases.Add(release);
-                }
-            }
-            catch (Exception ex)
-            {
-                OnParseError(results, ex);
-            }
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            throw new NotImplementedException();
-        }
-    }
-}
diff --git a/src/Jackett/Indexers/SceneAccess.cs b/src/Jackett/Indexers/SceneAccess.cs
index 176aed44e318361e1af118b3cd6e8025dc438a84..6add4bd218dd6bf9ea83c22b527c09ffa34d81df 100644
--- a/src/Jackett/Indexers/SceneAccess.cs
+++ b/src/Jackett/Indexers/SceneAccess.cs
@@ -17,29 +17,23 @@ namespace Jackett.Indexers
 {
     class SceneAccess : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-
-        private IWebClient webclient;
-        private string cookieHeader = "";
+        private string LoginUrl { get { return SiteLink + "login"; } }
+        private string SearchUrl { get { return SiteLink + "{0}?method=1&c{1}=1&search={2}"; } }
 
         public SceneAccess(IIndexerManagerService i, IWebClient c, Logger l)
             : base(name: "SceneAccess",
                 description: "Your gateway to the scene",
-                link: new Uri("https://sceneaccess.eu/"),
+                link: "https://sceneaccess.eu/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: c,
                 logger: l)
         {
-            LoginUrl = SiteLink + "login";
-            SearchUrl = SiteLink + "{0}?method=1&c{1}=1&search={2}";
-            webclient = c;
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -53,72 +47,26 @@ namespace Jackett.Indexers
                 { "submit", "come on in" }
             };
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            // Do the login
-            var response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                PostData = pairs,
-                Referer = LoginUrl,
-                Type = RequestType.POST,
-                Url = LoginUrl
-            });
-
-            cookieHeader = response.Cookies;
-
-            if (response.Status == HttpStatusCode.Found)
-            {
-                response = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                    Url = SiteLink.ToString(),
-                    Referer = LoginUrl.ToString(),
-                    Cookies = cookieHeader
-                });
-            }
+            var loginPage = await RequestStringWithCookies(LoginUrl, string.Empty);
 
-            if (!response.Content.Contains("nav_profile"))
+            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 = response.Content;
+                CQ dom = result.Content;
                 var messageEl = dom["#login_box_desc"];
                 var errorMessage = messageEl.Text().Trim();
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                configSaveData["cookie_header"] = cookieHeader;
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-        }
-
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
-        {
-            cookieHeader = (string)jsonConfig["cookie_header"];
-            if (!string.IsNullOrEmpty(cookieHeader))
-            {
-                IsConfigured = true;
-            }
+            });
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var searchSection = string.IsNullOrEmpty(query.Episode) ? "archive" : "browse";
             var searchCategory = string.IsNullOrEmpty(query.Episode) ? "26" : "27";
-
             var searchUrl = string.Format(SearchUrl, searchSection, searchCategory, searchString);
-
-
-            var results = await webclient.GetString(new Utils.Clients.WebRequest()
-            {
-                Referer = LoginUrl,
-                Cookies = cookieHeader,
-                Type = RequestType.GET,
-                Url = searchUrl
-            });
+            var results = await RequestStringWithCookiesAndRetry(searchUrl);
 
             try
             {
@@ -158,18 +106,7 @@ namespace Jackett.Indexers
                 OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public async Task<byte[]> Download(Uri link)
-        {
-            var response = await webclient.GetBytes(new Utils.Clients.WebRequest()
-            {
-                Url = link.ToString(),
-                Cookies = cookieHeader
-            });
-
-                return response.Content;
-            }
+            return releases;
         }
     }
+}
diff --git a/src/Jackett/Indexers/SceneTime.cs b/src/Jackett/Indexers/SceneTime.cs
index a7cb9aa1ae48d7836bdca48a43d0f2d943e59af7..0144dfc3a960eedf10a93ec132d2de5a4fc37d1f 100644
--- a/src/Jackett/Indexers/SceneTime.cs
+++ b/src/Jackett/Indexers/SceneTime.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,102 +19,62 @@ namespace Jackett.Indexers
 {
     public class SceneTime : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private readonly string DownloadUrl = "";
+        private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+        private string SearchUrl { get { return SiteLink + "browse_API.php"; } }
+        private string DownloadUrl { get { return SiteLink + "download.php/{0}/download.torrent"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public SceneTime(IIndexerManagerService i, Logger l)
+        public SceneTime(IIndexerManagerService i, Logger l, IWebClient w)
             : base(name: "SceneTime",
                 description: "Always on time",
-                link: new Uri("https://www.scenetime.com/"),
+                link: "https://www.scenetime.com/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: w,
                 logger: l)
         {
-            LoginUrl = SiteLink + "takelogin.php";
-            SearchUrl = SiteLink + "browse_API.php";
-            DownloadUrl = SiteLink + "download.php/{0}/download.torrent";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-           
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value }
+				{ "username", incomingConfig.Username.Value },
+				{ "password", incomingConfig.Password.Value }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("logout.php"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = result.Content;
                 var errorMessage = dom["td.text"].Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        private Dictionary<string, string> GetSearchFormData(string searchString)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        FormUrlEncodedContent GetSearchFormData(string searchString)
-        {
-            var pairs = new Dictionary<string, string> {
+            return new Dictionary<string, string> {
 				{ "c2", "1" }, { "c43", "1" }, { "c9", "1" }, { "c63", "1" }, { "c77", "1" }, { "c100", "1" }, { "c101", "1" },
                 { "cata", "yes" }, { "sec", "jax" },
                 { "search", searchString}
 			};
-            var content = new FormUrlEncodedContent(pairs);
-            return content;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
-
-            var searchContent = GetSearchFormData(searchString);
-            var response = await client.PostAsync(SearchUrl, searchContent);
-            var results = await response.Content.ReadAsStringAsync();
+            var results = await PostDataWithCookiesAndRetry(SearchUrl, GetSearchFormData(searchString));
 
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 var rows = dom["tr.browse"];
                 foreach (var row in rows)
                 {
@@ -148,14 +109,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/ShowRSS.cs b/src/Jackett/Indexers/ShowRSS.cs
index 6bb4f644bdea80f5bb6f9ba9c9941228a469228a..c6fffcf7cc1ff8baec4c628820d744a386abe30c 100644
--- a/src/Jackett/Indexers/ShowRSS.cs
+++ b/src/Jackett/Indexers/ShowRSS.cs
@@ -1,6 +1,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,37 +19,23 @@ namespace Jackett.Indexers
 {
     public class ShowRSS : BaseIndexer, IIndexer
     {
-        private readonly string searchAllUrl = "";
-        string BaseUrl;
+        private string searchAllUrl { get { return SiteLink + "feeds/all.rss"; } }
+        private string BaseUrl;
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public ShowRSS(IIndexerManagerService i, Logger l)
+        public ShowRSS(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "ShowRSS",
                 description: "showRSS is a service that allows you to keep track of your favorite TV shows",
-                link: new Uri("http://showrss.info"),
+                link: "http://showrss.info/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            searchAllUrl = SiteLink + "feeds/all.rss";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataUrl(SiteLink);
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink));
         }
 
         public async Task ApplyConfiguration(Newtonsoft.Json.Linq.JToken configJson)
@@ -58,7 +45,7 @@ namespace Jackett.Indexers
 
             var formattedUrl = config.GetFormattedHostUrl();
             var releases = await PerformQuery(new TorznabQuery(), formattedUrl);
-            if (releases.Length == 0)
+            if (releases.Count() == 0)
                 throw new Exception("Could not find releases from this URL");
 
             BaseUrl = formattedUrl;
@@ -69,50 +56,33 @@ namespace Jackett.Indexers
             IsConfigured = true;
         }
 
-        public void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(Newtonsoft.Json.Linq.JToken jsonConfig)
         {
             BaseUrl = (string)jsonConfig["base_url"];
             IsConfigured = true;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             return await PerformQuery(query, BaseUrl);
         }
 
-        public Task<byte[]> Download(Uri link)
+        public override Task<byte[]> Download(Uri link)
         {
             throw new NotImplementedException();
         }
 
-        private WebClient getWebClient()
-        {
-            WebClient wc = new WebClient();
-            WebHeaderCollection headers = new WebHeaderCollection();
-            headers.Add("User-Agent", BrowserUtil.ChromeUserAgent);
-            wc.Headers = headers;
-            return wc;
-        }
-
-        async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
+        async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(searchAllUrl);
-
-            XmlDocument xmlDoc = new XmlDocument();
-            string xml = string.Empty;
-            WebClient wc = getWebClient();
+            var result = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty);
+            var xmlDoc = new XmlDocument();
 
             try
             {
-                using (wc)
-                {
-                    xml = await wc.DownloadStringTaskAsync(new Uri(episodeSearchUrl));
-                    xmlDoc.LoadXml(xml);
-                }
-
+                xmlDoc.LoadXml(result.Content);
                 ReleaseInfo release;
                 string serie_title;
 
@@ -127,7 +97,9 @@ namespace Jackett.Indexers
                     release.Title = serie_title;
 
                     release.Comments = new Uri(node.SelectSingleNode("link").InnerText);
-                    release.Category = node.SelectSingleNode("title").InnerText;
+                    int category = 0;
+                    int.TryParse(node.SelectSingleNode("title").InnerText, out category);
+                    release.Category = category;
                     var test = node.SelectSingleNode("enclosure");
                     release.Guid = new Uri(test.Attributes["url"].Value);
                     release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture);
@@ -143,10 +115,10 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(xml, ex);
+                OnParseError(result.Content, ex);
             }
 
-            return releases.ToArray();
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/SpeedCD.cs b/src/Jackett/Indexers/SpeedCD.cs
index ba48e71619bc3f1936ae1a4a1da6e8878c6ec54c..bc4009333c15ecbf489af3a53ca51f44b81c66b5 100644
--- a/src/Jackett/Indexers/SpeedCD.cs
+++ b/src/Jackett/Indexers/SpeedCD.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -19,96 +20,56 @@ namespace Jackett.Indexers
 {
     public class SpeedCD : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
-        private readonly string SearchFormData = "c53=1&c49=1&c2=1&c52=1&c41=1&c50=1&c30=1&jxt=4&jxw=b";
-        private readonly string CommentsUrl = "";
-        private readonly string DownloadUrl = "";
-
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public SpeedCD(IIndexerManagerService i, Logger l)
+        private string LoginUrl { get { return SiteLink + "take_login.php"; } }
+        private string SearchUrl { get { return SiteLink + "V3/API/API.php"; } }
+        private string SearchFormData { get { return "c53=1&c49=1&c2=1&c52=1&c41=1&c50=1&c30=1&jxt=4&jxw=b"; } }
+        private string CommentsUrl { get { return SiteLink + "t/{0}"; } }
+        private string DownloadUrl { get { return SiteLink + "download.php?torrent={0}"; } }
+       
+        public SpeedCD(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "Speed.cd",
                 description: "Your home now!",
-                link: new Uri("http://speed.cd"),
+                link: "http://speed.cd/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            LoginUrl = SiteLink + "take_login.php";
-            SearchUrl = SiteLink + "V3/API/API.php";
-            CommentsUrl = SiteLink + "t/{0}";
-            DownloadUrl = SiteLink + "download.php?torrent={0}";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
-            var config = new ConfigurationDataBasicLogin();
-            config.LoadValuesFromJson(configJson);
-
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
             var pairs = new Dictionary<string, string> {
-				{ "username", config.Username.Value },
-				{ "password", config.Password.Value },
+				{ "username", incomingConfig.Username.Value },
+				{ "password", incomingConfig.Password.Value },
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("logout.php"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = result.Content;
                 var errorMessage = dom["h5"].First().Text().Trim();
-                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var formData = HttpUtility.ParseQueryString(SearchFormData);
             var formDict = formData.AllKeys.ToDictionary(t => t, t => formData[t]);
             formDict.Add("search", query.SanitizedSearchTerm);
-            var content = new FormUrlEncodedContent(formDict);
-
-            var response = await client.PostAsync(SearchUrl, content);
-            var results = await response.Content.ReadAsStringAsync();
-
+            var response = await PostDataWithCookiesAndRetry(SearchUrl, formDict);
             try
             {
-                var jsonResult = JObject.Parse(results);
+                var jsonResult = JObject.Parse(response.Content);
                 var resultArray = ((JArray)jsonResult["Fs"])[0]["Cn"]["torrents"];
                 foreach (var jobj in resultArray)
                 {
@@ -138,14 +99,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(response.Content, ex);
             }
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/Strike.cs b/src/Jackett/Indexers/Strike.cs
index 5c63476f27984136fb5c5b9f0f2dd17e6e1a21c2..997b55eacfd9cef34cecdefb88c05005638aab4a 100644
--- a/src/Jackett/Indexers/Strike.cs
+++ b/src/Jackett/Indexers/Strike.cs
@@ -1,6 +1,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -17,74 +18,57 @@ namespace Jackett.Indexers
 {
     public class Strike : BaseIndexer, IIndexer
     {
-        private readonly string DownloadUrl = "/torrents/api/download/{0}.torrent";
-        private readonly string SearchUrl = "/api/v2/torrents/search/?category=TV&phrase={0}";
-        private string BaseUrl;
+        private string DownloadUrl { get { return baseUri + "torrents/api/download/{0}.torrent"; } }
+        private string SearchUrl { get { return baseUri + "api/v2/torrents/search/?category=TV&phrase={0}"; } }
+        private string baseUrl = null;
+        private Uri baseUri { get { return new Uri(baseUrl); } }
 
-        private CookieContainer cookies;
-        private HttpClientHandler handler;
-        private HttpClient client;
-
-        public Strike(IIndexerManagerService i, Logger l)
+        public Strike(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "Strike",
                 description: "Torrent search engine",
-                link: new Uri("https://getstrike.net"),
+                link: "https://getstrike.net/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataUrl(SiteLink);
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink));
         }
 
-        public async Task ApplyConfiguration(JToken configJson)
+        public Task ApplyConfiguration(JToken configJson)
         {
             var config = new ConfigurationDataUrl(SiteLink);
             config.LoadValuesFromJson(configJson);
-
-            var formattedUrl = config.GetFormattedHostUrl();
-            var releases = await PerformQuery(new TorznabQuery(), formattedUrl);
-            if (releases.Length == 0)
-                throw new Exception("Could not find releases from this URL");
-
-            BaseUrl = formattedUrl;
-
+            baseUrl = config.GetFormattedHostUrl();
             var configSaveData = new JObject();
-            configSaveData["base_url"] = BaseUrl;
+            configSaveData["base_url"] = baseUrl;
             SaveConfig(configSaveData);
             IsConfigured = true;
+            return Task.FromResult(0);
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(JToken jsonConfig)
         {
-            BaseUrl = (string)jsonConfig["base_url"];
-            IsConfigured = true;
+            baseUrl = (string)jsonConfig["base_url"];
+            IsConfigured = !string.IsNullOrEmpty(baseUrl);
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             List<ReleaseInfo> releases = new List<ReleaseInfo>();
 
             var searchTerm = string.IsNullOrEmpty(query.SanitizedSearchTerm) ? "2015" : query.SanitizedSearchTerm;
 
             var searchString = searchTerm + " " + query.GetEpisodeSearchString();
-            var episodeSearchUrl = baseUrl + string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty);
             try
             {
-                var jResults = JObject.Parse(results);
+                var jResults = JObject.Parse(results.Content);
                 foreach (JObject result in (JArray)jResults["torrents"])
                 {
                     var release = new ReleaseInfo();
@@ -112,25 +96,20 @@ namespace Jackett.Indexers
 
                     release.InfoHash = (string)result["torrent_hash"];
                     release.MagnetUri = new Uri((string)result["magnet_uri"]);
-                    release.Link = new Uri(string.Format("{0}{1}", baseUrl, string.Format(DownloadUrl, release.InfoHash)));
+                    release.Link = new Uri(string.Format(DownloadUrl, release.InfoHash));
 
                     releases.Add(release);
                 }
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            return await PerformQuery(query, BaseUrl);
+            return releases;
         }
 
-        public Task<byte[]> Download(Uri link)
+        public override Task<byte[]> Download(Uri link)
         {
             throw new NotImplementedException();
         }
diff --git a/src/Jackett/Indexers/T411.cs b/src/Jackett/Indexers/T411.cs
index 81475355897a427adcbc9b2d304f9c9a838ee38d..17b2a6d673528f3ca0c321ab611ce3d45ee5c329 100644
--- a/src/Jackett/Indexers/T411.cs
+++ b/src/Jackett/Indexers/T411.cs
@@ -1,6 +1,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -32,12 +33,13 @@ namespace Jackett.Indexers
         string token = string.Empty;
         DateTime lastTokenFetch = DateTime.MinValue;
 
-        public T411(IIndexerManagerService i, Logger l)
+        public T411(IIndexerManagerService i, Logger l,IWebClient wc)
             : base(name: "T411",
                 description: "French Torrent Tracker",
-                link: new Uri("http://www.t411.io"),
+                link: "http://www.t411.io/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
             CommentsUrl = SiteLink + "/torrents/{0}";
@@ -107,7 +109,7 @@ namespace Jackett.Indexers
             IsConfigured = true;
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(JToken jsonConfig)
         {
             username = (string)jsonConfig["username"];
             password = (string)jsonConfig["password"];
@@ -116,10 +118,9 @@ namespace Jackett.Indexers
             IsConfigured = true;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchTerm = string.IsNullOrEmpty(query.SanitizedSearchTerm) ? "%20" : query.SanitizedSearchTerm;
             var searchString = searchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
@@ -164,10 +165,10 @@ namespace Jackett.Indexers
             {
                 OnParseError(results, ex);
             }
-            return releases.ToArray();
+            return releases;
         }
 
-        public async Task<byte[]> Download(Uri link)
+        public override async Task<byte[]> Download(Uri link)
         {
             var message = new HttpRequestMessage();
             message.Method = HttpMethod.Get;
diff --git a/src/Jackett/Indexers/ThePirateBay.cs b/src/Jackett/Indexers/ThePirateBay.cs
index e30169b19e5bef0aada4b23477a68cada0685257..f37a91f10284a4b7e27aa6b508f60d66c7328b1c 100644
--- a/src/Jackett/Indexers/ThePirateBay.cs
+++ b/src/Jackett/Indexers/ThePirateBay.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -19,37 +20,25 @@ namespace Jackett.Indexers
 {
     public class ThePirateBay : BaseIndexer, IIndexer
     {
-        const string SearchUrl = "/search/{0}/0/99/208,205";
-        string BaseUrl;
+        private const string SearchUrl = "/search/{0}/0/99/208,205";
+        private string BaseUrl;
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public ThePirateBay(IIndexerManagerService i, Logger l)
+        public ThePirateBay(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "The Pirate Bay",
                 description: "The worlds largest bittorrent indexer",
-                link: new Uri("https://thepiratebay.mn"),
+                link: "https://thepiratebay.mn/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
             BaseUrl = SiteLink.ToString();
             IsConfigured = false;
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataUrl(BaseUrl);
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(BaseUrl));
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -59,7 +48,7 @@ namespace Jackett.Indexers
 
             var formattedUrl = config.GetFormattedHostUrl();
             var releases = await PerformQuery(new TorznabQuery(), formattedUrl);
-            if (releases.Length == 0)
+            if (releases.Count() == 0)
                 throw new Exception("Could not find releases from this URL");
 
             BaseUrl = formattedUrl;
@@ -70,40 +59,28 @@ namespace Jackett.Indexers
             IsConfigured = true;
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(JToken jsonConfig)
         {
             BaseUrl = (string)jsonConfig["base_url"];
             IsConfigured = true;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             return await PerformQuery(query, BaseUrl);
         }
 
-        async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
+        async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl)
         {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var queryStr = HttpUtility.UrlEncode(searchString);
             var episodeSearchUrl = baseUrl + string.Format(SearchUrl, queryStr);
-
-            string results;
-
-            if (Engine.IsWindows)
-            {
-                results = await client.GetStringAsync(episodeSearchUrl);
-            }
-            else
-            {
-                var response = await CurlHelper.GetAsync(episodeSearchUrl, null, episodeSearchUrl);
-                results = Encoding.UTF8.GetString(response.Content);
-            }
+            var response = await RequestStringWithCookiesAndRetry(episodeSearchUrl, string.Empty);
 
             try
             {
-                CQ dom = results;
+                CQ dom = response.Content;
 
                 var rows = dom["#searchResult > tbody > tr"];
                 foreach (var row in rows)
@@ -162,18 +139,14 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                //  OnResultParsingError(this, results, ex);
-                throw ex;
+                OnParseError(response.Content, ex);
             }
             return releases.ToArray();
         }
 
-
-        public Task<byte[]> Download(Uri link)
+        public override Task<byte[]> Download(Uri link)
         {
             throw new NotImplementedException();
         }
-
-
     }
 }
diff --git a/src/Jackett/Indexers/TorrentBytes.cs b/src/Jackett/Indexers/TorrentBytes.cs
new file mode 100644
index 0000000000000000000000000000000000000000..03304012735f297459a62439299a8dc925019058
--- /dev/null
+++ b/src/Jackett/Indexers/TorrentBytes.cs
@@ -0,0 +1,176 @@
+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;
+
+namespace Jackett.Indexers
+{
+    public class TorrentBytes : BaseIndexer, IIndexer
+    {
+        private string BrowseUrl { get { return SiteLink + "browse.php"; } }
+        private string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+
+        public TorrentBytes(IIndexerManagerService i, IWebClient wc, Logger l)
+            : base(name: "TorrentBytes",
+                description: "A decade of torrentbytes",
+                link: "https://www.torrentbytes.net/",
+                caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                manager: i,
+                client: wc,
+                logger: l)
+        {
+           
+            AddCategoryMapping(41, TorznabCatType.TV);
+            AddCategoryMapping(33, TorznabCatType.TVSD);
+            AddCategoryMapping(38, TorznabCatType.TVHD);
+            AddCategoryMapping(32, TorznabCatType.TVSD);
+            AddCategoryMapping(37, TorznabCatType.TVSD);
+            AddCategoryMapping(44, TorznabCatType.TVSD);
+
+            AddCategoryMapping(40, TorznabCatType.Movies);
+            AddCategoryMapping(19, TorznabCatType.MoviesSD);
+            AddCategoryMapping(5, TorznabCatType.MoviesHD);
+            AddCategoryMapping(20, TorznabCatType.MoviesSD);
+            AddCategoryMapping(28, TorznabCatType.MoviesForeign);
+            AddCategoryMapping(45, TorznabCatType.MoviesSD);
+
+            AddCategoryMapping(43, TorznabCatType.Audio);
+            AddCategoryMapping(48, TorznabCatType.AudioLossless);
+            AddCategoryMapping(6, TorznabCatType.AudioLossy);
+            AddCategoryMapping(46, TorznabCatType.Movies);
+
+            AddCategoryMapping(1, TorznabCatType.Apps);
+            AddCategoryMapping(2, TorznabCatType.Apps);
+            AddCategoryMapping(23, TorznabCatType.Anime);
+            AddCategoryMapping(21, TorznabCatType.XXX);
+            AddCategoryMapping(9, TorznabCatType.XXXSD);
+            AddCategoryMapping(39, TorznabCatType.XXXHD);
+            AddCategoryMapping(29, TorznabCatType.XXXSD);
+            AddCategoryMapping(24, TorznabCatType.XXXImg);
+        }
+
+        public Task<ConfigurationData> GetConfigurationForSetup()
+        {
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
+        }
+
+        public async Task ApplyConfiguration(JToken configJson)
+        {
+            var incomingConfig = new ConfigurationDataBasicLogin();
+            incomingConfig.LoadValuesFromJson(configJson);
+            var pairs = new Dictionary<string, string> {
+                { "username", incomingConfig.Username.Value },
+                { "password", incomingConfig.Password.Value },
+                { "returnto", "/" },
+                { "login", "Log in!" }
+            };
+
+            var loginPage = await RequestStringWithCookies(SiteLink, string.Empty);
+
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, SiteLink, SiteLink);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
+            {
+                CQ dom = result.Content;
+                var messageEl = dom["body > div"].First();
+                var errorMessage = messageEl.Text().Trim();
+                throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)incomingConfig);
+            });
+        }
+
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
+        {
+            var releases = new List<ReleaseInfo>();
+            var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
+            var searchUrl = BrowseUrl;
+            var trackerCats = MapTorznabCapsToTrackers(query);
+            var queryCollection = new NameValueCollection();
+
+            // Tracker can only search OR return things in categories
+            if (!string.IsNullOrWhiteSpace(searchString))
+            {
+                queryCollection.Add("search", searchString);
+                queryCollection.Add("cat", "0");
+                queryCollection.Add("sc", "1");
+            }
+            else
+            {
+                foreach (var cat in MapTorznabCapsToTrackers(query))
+                {
+                    queryCollection.Add("c" + cat, "1");
+                }
+
+                queryCollection.Add("incldead", "0");
+            }
+
+            searchUrl += "?" + queryCollection.GetQueryString();
+
+            // 15 results per page - really don't want to call the server twice but only 15 results per page is a bit crap!
+            await ProcessPage(releases, searchUrl);  
+            await ProcessPage(releases, searchUrl + "&page=1");
+            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["#content table:eq(4) tr"];
+                foreach (var row in rows.Skip(1))
+                {
+                    var release = new ReleaseInfo();
+
+                    var link = row.Cq().Find("td:eq(1) a:eq(1)").First();
+                    release.Guid = new Uri(SiteLink + link.Attr("href"));
+                    release.Comments = release.Guid;
+                    release.Title = link.Text().Trim();
+                    release.Description = release.Title;
+
+                    // If we search an get no results, we still get a table just with no info.
+                    if (string.IsNullOrWhiteSpace(release.Title))
+                    {
+                        break;
+                    }
+
+                    var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
+                    release.Category = MapTrackerCatToNewznab(cat);
+
+
+                    var qLink = row.Cq().Find("td:eq(1) a").First();
+                    release.Link = new Uri(SiteLink + qLink.Attr("href"));
+
+                    var added = row.Cq().Find("td:eq(4)").First().Text().Trim();
+                    release.PublishDate = DateTimeUtil.FromTimeAgo(added);
+
+                    var sizeStr = row.Cq().Find("td:eq(6)").First().Text().Trim();
+                    release.Size = ReleaseInfo.GetBytes(sizeStr);
+
+                    release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(8)").First().Text().Trim());
+                    release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(9)").First().Text().Trim()) + release.Seeders;
+
+                    releases.Add(release);
+                }
+            }
+            catch (Exception ex)
+            {
+                OnParseError(results, ex);
+            }
+        }
+    }
+}
diff --git a/src/Jackett/Indexers/TorrentDay.cs b/src/Jackett/Indexers/TorrentDay.cs
index 7399366ec615954adb7d030eb36d5ff64be4a169..cbb9daefadc23830c8b2c4618db4b4a2eb2174c5 100644
--- a/src/Jackett/Indexers/TorrentDay.cs
+++ b/src/Jackett/Indexers/TorrentDay.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,49 +19,24 @@ namespace Jackett.Indexers
 {
     public class TorrentDay : BaseIndexer, IIndexer
     {
-        private readonly string StartPageUrl = "";
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
+        private string StartPageUrl { get { return SiteLink + "login.php"; } }
+        private string LoginUrl { get { return SiteLink + "tak3login.php"; } }
+        private string SearchUrl { get { return SiteLink + "browse.php?search={0}&cata=yes&c2=1&c7=1&c14=1&c24=1&c26=1&c31=1&c32=1&c33=1"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public TorrentDay(IIndexerManagerService i, Logger l)
+        public TorrentDay(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "TorrentDay",
                 description: "TorrentDay",
-                link: new Uri("https://torrentday.eu"),
+                link: "https://torrentday.eu/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            StartPageUrl = SiteLink + "login.php";
-            LoginUrl = SiteLink + "tak3login.php";
-            SearchUrl = SiteLink + "browse.php?search={0}&cata=yes&c2=1&c7=1&c14=1&c24=1&c26=1&c31=1&c32=1&c33=1";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
-        }
-
-        HttpRequestMessage CreateHttpRequest(string uri)
-        {
-            var message = new HttpRequestMessage();
-            message.Method = HttpMethod.Get;
-            message.RequestUri = new Uri(uri);
-            message.Headers.UserAgent.ParseAdd(BrowserUtil.ChromeUserAgent);
-            return message;
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -68,56 +44,35 @@ namespace Jackett.Indexers
             var config = new ConfigurationDataBasicLogin();
             config.LoadValuesFromJson(configJson);
 
-            var startMessage = CreateHttpRequest(StartPageUrl);
-            var results = await (await client.SendAsync(startMessage)).Content.ReadAsStringAsync();
+            var startMessage = await RequestStringWithCookies(StartPageUrl, string.Empty);
 
 
             var pairs = new Dictionary<string, string> {
 				{ "username", config.Username.Value },
 				{ "password", config.Password.Value }
 			};
-            var content = new FormUrlEncodedContent(pairs);
-            var loginRequest = CreateHttpRequest(LoginUrl);
-            loginRequest.Method = HttpMethod.Post;
-            loginRequest.Content = content;
-            loginRequest.Headers.Referrer = new Uri(StartPageUrl);
-
-            var response = await client.SendAsync(loginRequest);
-            var responseContent = await response.Content.ReadAsStringAsync();
 
-            if (!responseContent.Contains("logout.php"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, SiteLink, LoginUrl);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = result.Content;
                 var messageEl = dom["#login"];
                 messageEl.Children("form").Remove();
                 var errorMessage = messageEl.Text().Trim();
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
+
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 var rows = dom["#torrentTable > tbody > tr.browse"];
                 foreach (var row in rows)
                 {
@@ -146,14 +101,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/TorrentLeech.cs b/src/Jackett/Indexers/TorrentLeech.cs
index 52c4bc4bc948675d6ee7382aaa9a51442a627793..3067861008777b085ac964676970c15ef24c0ce8 100644
--- a/src/Jackett/Indexers/TorrentLeech.cs
+++ b/src/Jackett/Indexers/TorrentLeech.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,45 +19,29 @@ namespace Jackett.Indexers
 {
     public class TorrentLeech : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
+        private string LoginUrl { get { return SiteLink + "user/account/login/"; } }
+        private string SearchUrl { get { return SiteLink + "torrents/browse/index/query/{0}/categories/2%2C26%2C27%2C32/orderby/added?"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public TorrentLeech(IIndexerManagerService i, Logger l)
+        public TorrentLeech(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "TorrentLeech",
                 description: "This is what happens when you seed",
-                link: new Uri("http://www.torrentleech.org"),
+                link: "http://www.torrentleech.org/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-            LoginUrl = SiteLink + "user/account/login/";
-            SearchUrl = SiteLink + "torrents/browse/index/query/{0}/categories/2%2C26%2C27%2C32/orderby/added?";
-
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
         {
             var config = new ConfigurationDataBasicLogin();
             config.LoadValuesFromJson(configJson);
-
             var pairs = new Dictionary<string, string> {
 				{ "username", config.Username.Value },
 				{ "password", config.Password.Value },
@@ -64,43 +49,25 @@ namespace Jackett.Indexers
                 { "login", "submit" }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("/user/account/logout"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true,null, LoginUrl);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("/user/account/logout"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = result.Content;
                 var messageEl = dom[".ui-state-error"].Last();
                 var errorMessage = messageEl.Text().Trim();
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
 
                 CQ qRows = dom["#torrenttable > tbody > tr"];
 
@@ -140,18 +107,10 @@ namespace Jackett.Indexers
 
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
 
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
-
-
-
     }
 }
diff --git a/src/Jackett/Indexers/TorrentShack.cs b/src/Jackett/Indexers/TorrentShack.cs
index 97eb3f99a287969032a5604a905ddce767cb0aaf..3823ce379022e8eae1838a25f8e21fc8e9b0f5c0 100644
--- a/src/Jackett/Indexers/TorrentShack.cs
+++ b/src/Jackett/Indexers/TorrentShack.cs
@@ -2,6 +2,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -18,37 +19,23 @@ namespace Jackett.Indexers
 {
     public class TorrentShack : BaseIndexer, IIndexer
     {
-        private readonly string LoginUrl = "";
-        private readonly string SearchUrl = "";
+        private string LoginUrl { get { return SiteLink + "login.php"; } }
+        private string SearchUrl { get { return SiteLink + "torrents.php?searchstr={0}&release_type=both&searchtags=&tags_type=0&order_by=s3&order_way=desc&torrent_preset=all&filter_cat%5B600%5D=1&filter_cat%5B620%5D=1&filter_cat%5B700%5D=1&filter_cat%5B981%5D=1&filter_cat%5B980%5D=1"; } }
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public TorrentShack(IIndexerManagerService i, Logger l)
+        public TorrentShack(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "TorrentShack",
                 description: "TorrentShack",
-                link: new Uri("http://torrentshack.me"),
+                link: "http://torrentshack.me/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
+                client: wc,
                 manager: i,
                 logger: l)
         {
-            LoginUrl = SiteLink + "login.php";
-            SearchUrl = SiteLink + "torrents.php?searchstr={0}&release_type=both&searchtags=&tags_type=0&order_by=s3&order_way=desc&torrent_preset=all&filter_cat%5B600%5D=1&filter_cat%5B620%5D=1&filter_cat%5B700%5D=1&filter_cat%5B981%5D=1&filter_cat%5B980%5D=1";
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataBasicLogin();
-            return Task.FromResult<ConfigurationData>(config);
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataBasicLogin());
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -63,46 +50,26 @@ namespace Jackett.Indexers
                 { "login", "Login" }
 			};
 
-            var content = new FormUrlEncodedContent(pairs);
-
-            var response = await client.PostAsync(LoginUrl, content);
-            var responseContent = await response.Content.ReadAsStringAsync();
-
-            if (!responseContent.Contains("logout.php"))
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, LoginUrl);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
             {
-                CQ dom = responseContent;
+                CQ dom = result.Content;
                 var messageEl = dom["#loginform"];
                 messageEl.Children("table").Remove();
                 var errorMessage = messageEl.Text().Trim();
                 throw new ExceptionWithConfigData(errorMessage, (ConfigurationData)config);
-            }
-            else
-            {
-                var configSaveData = new JObject();
-                cookies.DumpToJson(SiteLink, configSaveData);
-                SaveConfig(configSaveData);
-                IsConfigured = true;
-            }
-
+            });
         }
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
-            cookies.FillFromJson(SiteLink, jsonConfig, logger);
-            IsConfigured = true;
-        }
-
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString));
-            var results = await client.GetStringAsync(episodeSearchUrl);
+            var results = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
             try
             {
-                CQ dom = results;
+                CQ dom = results.Content;
                 var rows = dom["#torrent_table > tbody > tr.torrent"];
                 foreach (var row in rows)
                 {
@@ -130,14 +97,9 @@ namespace Jackett.Indexers
             }
             catch (Exception ex)
             {
-                OnParseError(results, ex);
+                OnParseError(results.Content, ex);
             }
-            return releases.ToArray();
-        }
-
-        public Task<byte[]> Download(Uri link)
-        {
-            return client.GetByteArrayAsync(link);
+            return releases;
         }
     }
 }
diff --git a/src/Jackett/Indexers/Torrentz.cs b/src/Jackett/Indexers/Torrentz.cs
index b0246d82d1eb2563f74ec93ac4f0979134d85d85..c1d846da42547a7cb39176e9212388b347387c7d 100644
--- a/src/Jackett/Indexers/Torrentz.cs
+++ b/src/Jackett/Indexers/Torrentz.cs
@@ -1,6 +1,7 @@
 using Jackett.Models;
 using Jackett.Services;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -12,43 +13,29 @@ using System.Threading.Tasks;
 using System.Web;
 using System.Windows.Forms;
 using System.Xml;
+using System.Linq;
 
 namespace Jackett.Indexers
 {
     public class Torrentz : BaseIndexer, IIndexer
     {
-        private readonly string SearchUrl = "";
-        string BaseUrl;
+        private string SearchUrl { get { return SiteLink + "feed_verifiedP?f={0}"; } }
+        private string BaseUrl;
 
-        CookieContainer cookies;
-        HttpClientHandler handler;
-        HttpClient client;
-
-        public Torrentz(IIndexerManagerService i, Logger l)
+        public Torrentz(IIndexerManagerService i, Logger l, IWebClient wc)
             : base(name: "Torrentz",
                 description: "Torrentz is a meta-search engine and a Multisearch. This means we just search other search engines.",
-                link: new Uri("https://torrentz.eu"),
+                link: "https://torrentz.eu/",
                 caps: TorznabCapsUtil.CreateDefaultTorznabTVCaps(),
                 manager: i,
+                client: wc,
                 logger: l)
         {
-
-            SearchUrl = SiteLink + "feed_verifiedP?f={0}";
-            cookies = new CookieContainer();
-            handler = new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = true,
-                UseCookies = true,
-            };
-            client = new HttpClient(handler);
         }
 
         public Task<ConfigurationData> GetConfigurationForSetup()
         {
-            var config = new ConfigurationDataUrl(SiteLink);
-            return Task.FromResult<ConfigurationData>(config);
-
+            return Task.FromResult<ConfigurationData>(new ConfigurationDataUrl(SiteLink));
         }
 
         public async Task ApplyConfiguration(JToken configJson)
@@ -57,46 +44,29 @@ namespace Jackett.Indexers
             config.LoadValuesFromJson(configJson);
 
             var formattedUrl = config.GetFormattedHostUrl();
-            var releases = await PerformQuery(new TorznabQuery(), formattedUrl);
-            if (releases.Length == 0)
+            IEnumerable<ReleaseInfo> releases = await PerformQuery(new TorznabQuery(), formattedUrl);
+            if (releases.Count() == 0)
                 throw new Exception("Could not find releases from this URL");
 
             BaseUrl = formattedUrl;
-
             var configSaveData = new JObject();
             configSaveData["base_url"] = BaseUrl;
             SaveConfig(configSaveData);
             IsConfigured = true;
-
         }
 
-        private WebClient getWebClient()
+        async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query, string baseUrl)
         {
-            WebClient wc = new WebClient();
-            WebHeaderCollection headers = new WebHeaderCollection();
-            headers.Add("User-Agent", BrowserUtil.ChromeUserAgent);
-            wc.Headers = headers;
-            return wc;
-        }
-
-        async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query, string baseUrl)
-        {
-            List<ReleaseInfo> releases = new List<ReleaseInfo>();
-
+            var releases = new List<ReleaseInfo>();
             var searchString = query.SanitizedSearchTerm + " " + query.GetEpisodeSearchString();
             var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString.Trim()));
-
-            XmlDocument xmlDoc = new XmlDocument();
+            var xmlDoc = new XmlDocument();
             string xml = string.Empty;
-            WebClient wc = getWebClient();
+            var result = await RequestStringWithCookiesAndRetry(episodeSearchUrl);
 
             try
             {
-                using (wc)
-                {
-                    xml = await wc.DownloadStringTaskAsync(new Uri(episodeSearchUrl));
-                    xmlDoc.LoadXml(xml);
-                }
+                xmlDoc.LoadXml(result.Content);
 
                 ReleaseInfo release;
                 TorrentzHelper td;
@@ -112,7 +82,9 @@ namespace Jackett.Indexers
                     release.Title = serie_title;
 
                     release.Comments = new Uri(node.SelectSingleNode("link").InnerText);
-                    release.Category = node.SelectSingleNode("category").InnerText;
+                    int category = 0;
+                    int.TryParse(node.SelectSingleNode("category").InnerText, out category);
+                    release.Category = category;
                     release.Guid = new Uri(node.SelectSingleNode("guid").InnerText);
                     release.PublishDate = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText, CultureInfo.InvariantCulture);
 
@@ -131,22 +103,22 @@ namespace Jackett.Indexers
                 OnParseError(xml, ex);
             }
 
-            return releases.ToArray();
+            return releases;
         }
 
 
-        public void LoadFromSavedConfiguration(JToken jsonConfig)
+        public override void LoadFromSavedConfiguration(JToken jsonConfig)
         {
             BaseUrl = (string)jsonConfig["base_url"];
             IsConfigured = true;
         }
 
-        public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query)
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
         {
             return await PerformQuery(query, BaseUrl);
         }
 
-        public Task<byte[]> Download(Uri link)
+        public override Task<byte[]> Download(Uri link)
         {
             throw new NotImplementedException();
         }
@@ -206,7 +178,7 @@ namespace Jackett.Indexers
                 switch (counter)
                 {
                     case 0:
-                        this.Size = ReleaseInfo.BytesFromMB(ParseUtil.CoerceLong(val.Substring(0, val.IndexOf(" ") - 1)));
+                        this.Size = ReleaseInfo.GetBytes(val); 
                         break;
                     case 1:
                         this.Seeders = ParseUtil.CoerceInt(val.Contains(",") ? val.Remove(val.IndexOf(","), 1) : val);
diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj
index 9191460f1b4e3a70f130408ca2490a95b4de07d0..3762f618ba73ac8b40453c82d679a2bfcb9d24be 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Jackett</RootNamespace>
     <AssemblyName>Jackett</AssemblyName>
-    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <TargetFrameworkProfile />
     <PublishUrl>publish\</PublishUrl>
@@ -62,15 +62,17 @@
       <HintPath>..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.Owin">
+    <Reference Include="Autofac.Integration.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.Owin.3.1.0\lib\net45\Autofac.Integration.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Autofac.Integration.WebApi, Version=3.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="Autofac.Integration.WebApi.Owin">
+    <Reference Include="Autofac.Integration.WebApi.Owin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
       <HintPath>..\packages\Autofac.WebApi2.Owin.3.2.0\lib\net45\Autofac.Integration.WebApi.Owin.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="AutoMapper, Version=3.3.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
       <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll</HintPath>
@@ -80,11 +82,13 @@
       <HintPath>..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="CsQuery">
+    <Reference Include="CsQuery, Version=1.3.3.249, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll</HintPath>
+      <Private>True</Private>
     </Reference>
-    <Reference Include="Microsoft.AspNet.Identity.Core">
+    <Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
@@ -98,8 +102,9 @@
       <HintPath>..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Microsoft.Owin.Host.SystemWeb">
+    <Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="Microsoft.Owin.Hosting, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
@@ -109,6 +114,14 @@
       <HintPath>..\packages\Microsoft.Owin.StaticFiles.3.0.1\lib\net45\Microsoft.Owin.StaticFiles.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="NLog.Windows.Forms, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\NLog.Windows.Forms.2.0.0.0\lib\net35\NLog.Windows.Forms.dll</HintPath>
       <Private>True</Private>
@@ -122,15 +135,17 @@
     <Reference Include="System.Core" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.Net.Http" />
-    <Reference Include="System.Net.Http.Extensions">
+    <Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="System.Net.Http.Primitives">
+    <Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Net.Http.WebRequest" />
     <Reference Include="System.ServiceProcess" />
@@ -143,8 +158,9 @@
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="System.Web.Http.Tracing">
+    <Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml.Linq" />
@@ -152,31 +168,58 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
     <Reference Include="System.Xml" />
-    <Reference Include="Newtonsoft.Json">
-      <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
-    </Reference>
-    <Reference Include="NLog">
-      <HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
-    </Reference>
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Controllers\APIController.cs" />
+    <Compile Include="Controllers\PotatoController.cs" />
+    <Compile Include="Controllers\TorznabController.cs" />
     <Compile Include="Controllers\DownloadController.cs" />
     <Compile Include="Engine.cs" />
+    <Compile Include="Indexers\AlphaRatio.cs" />
     <Compile Include="Indexers\BakaBT.cs" />
     <Compile Include="Indexers\BaseIndexer.cs" />
     <Compile Include="Indexers\BB.cs" />
+    <Compile Include="Indexers\BeyondHD.cs" />
+    <Compile Include="Indexers\BitHdtv.cs" />
+    <Compile Include="Indexers\BitMeTV.cs" />
+    <Compile Include="Indexers\FrenchTorrentDb.cs" />
+    <Compile Include="Indexers\Freshon.cs" />
     <Compile Include="Indexers\HDSpace.cs" />
+    <Compile Include="Indexers\HDTorrents.cs" />
+    <Compile Include="Indexers\IIndexer.cs" />
+    <Compile Include="Indexers\ImmortalSeed.cs" />
+    <Compile Include="Indexers\TorrentBytes.cs" />
+    <Compile Include="Indexers\IPTorrents.cs" />
+    <Compile Include="Indexers\MoreThanTV.cs" />
     <Compile Include="Indexers\Pretome.cs" />
     <Compile Include="Indexers\PrivateHD.cs" />
+    <Compile Include="Indexers\SceneAccess.cs" />
+    <Compile Include="Indexers\SceneTime.cs" />
+    <Compile Include="Indexers\ShowRSS.cs" />
     <Compile Include="Indexers\SpeedCD.cs" />
+    <Compile Include="Indexers\Strike.cs" />
+    <Compile Include="Indexers\T411.cs" />
+    <Compile Include="Indexers\ThePirateBay.cs" />
+    <Compile Include="Indexers\TorrentDay.cs" />
+    <Compile Include="Indexers\TorrentLeech.cs" />
+    <Compile Include="Indexers\TorrentShack.cs" />
+    <Compile Include="Indexers\Torrentz.cs" />
     <Compile Include="Models\CachedResult.cs" />
+    <Compile Include="Models\CategoryMapping.cs" />
+    <Compile Include="Models\IndexerConfig\BmtvConfig.cs" />
+    <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginAnimeBytes.cs" />
+    <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginFrenchTorrentDb.cs" />
+    <Compile Include="Models\IndexerConfig\PretomeConfiguration.cs" />
+    <Compile Include="Models\TorrentPotatoRequest.cs" />
+    <Compile Include="Models\TorrentPotatoResponse.cs" />
+    <Compile Include="Models\TorrentPotatoResponseItem.cs" />
     <Compile Include="Models\TorznabCapabilities.cs" />
     <Compile Include="Models\Config\ServerConfig.cs" />
     <Compile Include="Models\TorznabCategory.cs" />
+    <Compile Include="Models\TorznabCatType.cs" />
     <Compile Include="Models\TrackerCache.cs" />
     <Compile Include="Models\TrackerCacheResult.cs" />
     <Compile Include="Services\CacheService.cs" />
+    <Compile Include="Utils\Clients\BaseWebResult.cs" />
     <Compile Include="Utils\Clients\UnixLibCurlWebClient.cs" />
     <Compile Include="Utils\Clients\WebByteResult.cs" />
     <Compile Include="Utils\Clients\WebClientResult.cs" />
@@ -198,31 +241,12 @@
     <Compile Include="Utils\DataUrl.cs" />
     <Compile Include="ExceptionWithConfigData.cs" />
     <Compile Include="HttpClientExtensions.cs" />
-    <Compile Include="Indexers\IIndexer.cs" />
-    <Compile Include="Indexers\BeyondHD.cs" />
-    <Compile Include="Indexers\BitHdtv.cs" />
-    <Compile Include="Indexers\BitMeTV.cs" />
-    <Compile Include="Indexers\FrenchTorrentDb.cs" />
-    <Compile Include="Indexers\Freshon.cs" />
-    <Compile Include="Indexers\HDTorrents.cs" />
-    <Compile Include="Indexers\IPTorrents.cs" />
-    <Compile Include="Indexers\MoreThanTV.cs" />
-    <Compile Include="Indexers\Rarbg.cs" />
-    <Compile Include="Indexers\SceneAccess.cs" />
-    <Compile Include="Indexers\SceneTime.cs" />
-    <Compile Include="Indexers\ShowRSS.cs" />
-    <Compile Include="Indexers\Strike.cs" />
-    <Compile Include="Indexers\T411.cs" />
-    <Compile Include="Indexers\ThePirateBay.cs" />
-    <Compile Include="Indexers\TorrentDay.cs" />
     <Compile Include="Indexers\AnimeBytes.cs" />
-    <Compile Include="Indexers\TorrentLeech.cs" />
-    <Compile Include="Indexers\TorrentShack.cs" />
-    <Compile Include="Indexers\Torrentz.cs" />
     <Compile Include="JackettModule.cs" />
     <Compile Include="Utils\DateTimeUtil.cs" />
     <Compile Include="Utils\Clients\IWebClient.cs" />
     <Compile Include="Utils\JackettAuthorizedAttribute.cs" />
+    <Compile Include="Utils\JsonContent.cs" />
     <Compile Include="Utils\ParseUtil.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
@@ -239,17 +263,19 @@
     <Compile Include="Startup.cs" />
     <Compile Include="Models\TorznabQuery.cs" />
     <Compile Include="CurlHelper.cs" />
-    <Compile Include="Indexers\AlphaRatio.cs" />
     <Compile Include="Utils\StringUtil.cs" />
     <Compile Include="Utils\TorznabCapsUtil.cs" />
     <Compile Include="Utils\Clients\UnixSafeCurlWebClient.cs" />
     <Compile Include="Utils\WebApiRootRedirectMiddleware.cs" />
     <Compile Include="Utils\WebAPIRequestLogger.cs" />
     <Compile Include="Utils\WebAPIToNLogTracer.cs" />
-    <Compile Include="Utils\Clients\WindowsWebClient.cs" />
+    <Compile Include="Utils\Clients\HttpWebClient.cs" />
+    <Compile Include="WebAPIExceptionLogger.cs" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="App.config" />
+    <None Include="App.config">
+      <SubType>Designer</SubType>
+    </None>
     <None Include="Content\fonts\fontawesome-webfont.eot">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
@@ -274,14 +300,12 @@
     <None Include="Content\fonts\glyphicons-halflings-regular.woff2">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="packages.config">
-      <SubType>Designer</SubType>
-    </None>
     <None Include="Content\fonts\glyphicons-halflings-regular.woff">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
   </ItemGroup>
   <ItemGroup>
+    <None Include="packages.config" />
     <None Include="Resources\test.xml" />
   </ItemGroup>
   <ItemGroup>
@@ -363,6 +387,9 @@
     <Content Include="Content\logos\hdtorrents.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Content\logos\immortalseed.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="Content\logos\pretome.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -384,6 +411,9 @@
     <Content Include="Content\logos\t411.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Content\logos\torrentbytes.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="Content\logos\torrentday.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -441,9 +471,6 @@
     <Content Include="Content\logos\morethantv.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="Content\logos\rarbg.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="Content\logos\strike.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -505,6 +532,7 @@
       </Properties>
     </MonoDevelop>
   </ProjectExtensions>
+  <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
   <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
     <PropertyGroup>
@@ -512,5 +540,4 @@
     </PropertyGroup>
     <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
   </Target>
-  <Import Project="..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets" Condition="Exists('..\packages\AutoMapper.3.3.1\tools\AutoMapper.targets')" />
 </Project>
\ No newline at end of file
diff --git a/src/Jackett/JackettModule.cs b/src/Jackett/JackettModule.cs
index 23cfae951aea0dd975f17d0c83123ba2dfe344c0..74790ad76f2cc2a29499db1bdc20cbf0dd562c2f 100644
--- a/src/Jackett/JackettModule.cs
+++ b/src/Jackett/JackettModule.cs
@@ -8,6 +8,8 @@ using Autofac.Integration.WebApi;
 using Jackett.Indexers;
 using Jackett.Utils;
 using Jackett.Utils.Clients;
+using AutoMapper;
+using Jackett.Models;
 
 namespace Jackett
 {
@@ -20,18 +22,29 @@ namespace Jackett
             builder.RegisterAssemblyTypes(thisAssembly).Except<IIndexer>().AsImplementedInterfaces().SingleInstance();
             builder.RegisterApiControllers(thisAssembly).InstancePerRequest();
 
-            // Register the best web client for the platform or exec curl as a safe option
-            if (Startup.CurlSafe)
+            // Register the best web client for the platform or the override
+            switch (Startup.ClientOverride)
             {
-                builder.RegisterType<UnixSafeCurlWebClient>().As<IWebClient>();
-            }
-            else if(System.Environment.OSVersion.Platform == PlatformID.Unix)
-            {
-                builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>();
-            }
-            else
-            {
-                builder.RegisterType<WindowsWebClient>().As<IWebClient>();
+                case "httpclient":
+                    builder.RegisterType<HttpWebClient>().As<IWebClient>();
+                    break;
+                case "safecurl":
+                    builder.RegisterType<UnixSafeCurlWebClient>().As<IWebClient>();
+                    break;
+                case "libcurl":
+                    builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>();
+                    break;
+                case "automatic":
+                    default:
+                    if (System.Environment.OSVersion.Platform == PlatformID.Unix)
+                    {
+                        builder.RegisterType<UnixLibCurlWebClient>().As<IWebClient>();
+                    }
+                    else
+                    {
+                        builder.RegisterType<HttpWebClient>().As<IWebClient>();
+                    }
+                    break;
             }
 
             // Register indexers
@@ -41,6 +54,27 @@ namespace Jackett
             {
                 builder.RegisterType(indexer).Named<IIndexer>(BaseIndexer.GetIndexerID(indexer));
             }
+
+            Mapper.CreateMap<WebClientByteResult, WebClientStringResult>().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((be, str) =>
+            {
+                str.Content = Encoding.UTF8.GetString(be.Content);
+            });
+
+            Mapper.CreateMap<WebClientStringResult, WebClientByteResult>().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((str, be) =>
+            {
+                if (!string.IsNullOrEmpty(str.Content))
+                {
+                    be.Content = Encoding.UTF8.GetBytes(str.Content);
+                }
+            });
+
+            Mapper.CreateMap<WebClientStringResult, WebClientStringResult>();
+            Mapper.CreateMap<WebClientByteResult, WebClientByteResult>();
+
+            Mapper.CreateMap<ReleaseInfo, TrackerCacheResult>().AfterMap((r, t) =>
+            {
+                t.CategoryDesc = TorznabCatType.GetCatDesc(r.Category);
+            });
         }
     }
 }
diff --git a/src/Jackett/Models/CategoryMapping.cs b/src/Jackett/Models/CategoryMapping.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6ab314c4841e84d2dbacc4d926ad0377c8a0d76d
--- /dev/null
+++ b/src/Jackett/Models/CategoryMapping.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models
+{
+    class CategoryMapping
+    {
+        public CategoryMapping(string trackerCat, int newzCat)
+        {
+            TrackerCategory = trackerCat.ToLowerInvariant();
+            NewzNabCategory = newzCat;
+        }
+
+        public string TrackerCategory { get; private set; }
+        public int NewzNabCategory { get; private set; }
+    }
+}
diff --git a/src/Jackett/Models/Config/ServerConfig.cs b/src/Jackett/Models/Config/ServerConfig.cs
index 58a221a5f051d955d10a1898fa141fe5c562c3f7..36f8e1d39d2f2bc3527105c2c786aa3a1526bb53 100644
--- a/src/Jackett/Models/Config/ServerConfig.cs
+++ b/src/Jackett/Models/Config/ServerConfig.cs
@@ -12,6 +12,7 @@ namespace Jackett.Models.Config
         public ServerConfig()
         {
             Port = 9117;
+            AllowExternal = System.Environment.OSVersion.Platform == PlatformID.Unix;
         }
 
         public int Port { get; set; }
diff --git a/src/Jackett/Models/ConfigurationData.cs b/src/Jackett/Models/ConfigurationData.cs
index 21344cf9c68decad3e35fb85741d47dd0acf8833..be18414467e1daea1e03638bf042610602ffa41e 100644
--- a/src/Jackett/Models/ConfigurationData.cs
+++ b/src/Jackett/Models/ConfigurationData.cs
@@ -19,6 +19,16 @@ namespace Jackett.Models
             HiddenData
         }
 
+        public ConfigurationData()
+        {
+
+        }
+
+        public ConfigurationData(JToken json)
+        {
+            LoadValuesFromJson(json);
+        }
+
         public void LoadValuesFromJson(JToken json)
         {
             // todo: match up ids with items and fill values
@@ -33,6 +43,9 @@ namespace Jackett.Models
                     case ItemType.InputBool:
                         ((BoolItem)item).Value = (bool)dictionary[item.ID];
                         break;
+                    case ItemType.HiddenData:
+                        ((HiddenItem)item).Value = (string)dictionary[item.ID];
+                        break;
                 }
             }
         }
@@ -74,6 +87,15 @@ namespace Jackett.Models
             public string ID { get { return Name.Replace(" ", "").ToLower(); } }
         }
 
+        public class HiddenItem : StringItem
+        {
+            public HiddenItem(string value)
+            {
+                Value = value;
+                ItemType = ItemType.HiddenData;
+            }
+        }
+
         public class DisplayItem : StringItem
         {
             public DisplayItem(string value)
diff --git a/src/Jackett/Models/ConfigurationDataBasicLogin.cs b/src/Jackett/Models/ConfigurationDataBasicLogin.cs
index 66afbffe358b5e333a2b426786c89b41bd74eff6..fc75502847b81ae7997ee5c8bd2dc3cb88c01cee 100644
--- a/src/Jackett/Models/ConfigurationDataBasicLogin.cs
+++ b/src/Jackett/Models/ConfigurationDataBasicLogin.cs
@@ -1,4 +1,5 @@
-using System;
+using Newtonsoft.Json.Linq;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
diff --git a/src/Jackett/Models/IndexerConfig/BmtvConfig.cs b/src/Jackett/Models/IndexerConfig/BmtvConfig.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c7f1af5587b855bd543e514d072427fcf103e52e
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/BmtvConfig.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models.IndexerConfig
+{
+    class BmtvConfig : ConfigurationData
+    {
+        public StringItem Username { get; private set; }
+
+        public StringItem Password { get; private set; }
+
+        public ImageItem CaptchaImage { get; private set; }
+
+        public StringItem CaptchaText { get; private set; }
+
+        public HiddenItem CaptchaCookie { get; private set; }
+
+        public BmtvConfig()
+        {
+            Username = new StringItem { Name = "Username" };
+            Password = new StringItem { Name = "Password" };
+            CaptchaImage = new ImageItem { Name = "Captcha Image" };
+            CaptchaText = new StringItem { Name = "Captcha Text" };
+            CaptchaCookie = new HiddenItem("") { Name = "Captcha Cookie" };
+        }
+
+        public override Item[] GetItems()
+        {
+            return new Item[] { Username, Password, CaptchaImage, CaptchaText, CaptchaCookie };
+        }
+    }
+}
diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs
new file mode 100644
index 0000000000000000000000000000000000000000..249bec8d76cdaa448dbcdec85513b610da57a405
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginAnimeBytes.cs
@@ -0,0 +1,27 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models.IndexerConfig
+{
+    class ConfigurationDataBasicLoginAnimeBytes : ConfigurationDataBasicLogin
+    {
+        public BoolItem IncludeRaw { get; private set; }
+        public DisplayItem DateWarning { get; private set; }
+
+        public ConfigurationDataBasicLoginAnimeBytes()
+            : 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" };
+        }
+
+        public override Item[] GetItems()
+        {
+            return new Item[] { Username, Password, IncludeRaw, DateWarning };
+        }
+    }
+}
diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9cfbb6d6dd9a41b907148b3daf444a0d8b020729
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginFrenchTorrentDb.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models.IndexerConfig
+{
+    class ConfigurationDataBasicLoginFrenchTorrentDb : ConfigurationData
+    {
+        public StringItem Cookie { get; private set; }
+
+        public ConfigurationDataBasicLoginFrenchTorrentDb()
+        {
+            Cookie = new StringItem { Name = "Cookie" };
+        }
+
+        public override Item[] GetItems()
+        {
+            return new Item[] { Cookie };
+        }
+    }
+}
diff --git a/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs b/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs
new file mode 100644
index 0000000000000000000000000000000000000000..27cb36688df40f1a868e670a51ef75346a22f168
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/PretomeConfiguration.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models.IndexerConfig
+{
+    class PretomeConfiguration : ConfigurationDataBasicLogin
+    {
+        public StringItem Pin { get; private set; }
+
+        public PretomeConfiguration() : base()
+        {
+            Pin = new StringItem { Name = "Login Pin Number" };
+        }
+
+        public override Item[] GetItems()
+        {
+            return new Item[] { Pin, Username, Password };
+        }
+    }
+}
diff --git a/src/Jackett/Models/ReleaseInfo.cs b/src/Jackett/Models/ReleaseInfo.cs
index a12dac0bfea5c656f8429e9ea99f8ff9c1375373..2e24293b391a6cc972bb328713b8d68409b0c982 100644
--- a/src/Jackett/Models/ReleaseInfo.cs
+++ b/src/Jackett/Models/ReleaseInfo.cs
@@ -16,7 +16,7 @@ namespace Jackett.Models
         public Uri Link { get; set; }
         public Uri Comments { get; set; }
         public DateTime PublishDate { get; set; }
-        public string Category { get; set; }
+        public int Category { get; set; }
         public long? Size { get; set; }
         public string Description { get; set; }
         public long? RageID { get; set; }
diff --git a/src/Jackett/Models/ResultPage.cs b/src/Jackett/Models/ResultPage.cs
index fe238e2252311bdb84d1faf241d0a949193064c5..7593ea0b66b31c76c82d313af9c1f49279bfeb2d 100644
--- a/src/Jackett/Models/ResultPage.cs
+++ b/src/Jackett/Models/ResultPage.cs
@@ -63,8 +63,6 @@ namespace Jackett.Models
                             new XElement("link", ChannelInfo.ImageLink.ToString()),
                             new XElement("description", ChannelInfo.ImageDescription)
                         ),
-
-
                         from r in Releases
                         select new XElement("item",
                             new XElement("title", r.Title),
@@ -74,7 +72,7 @@ namespace Jackett.Models
                             r.Size == null ? null : new XElement("size", r.Size),
                             new XElement("description", r.Description),
                             new XElement("link", r.Link ?? r.MagnetUri),
-                            r.Category == null ? null : new XElement("category", r.Category),
+                            r.Category == 0 ? null : new XElement("category", r.Category),
                             new XElement(
                                 "enclosure",
                                 new XAttribute("url", r.Link ?? r.MagnetUri),
diff --git a/src/Jackett/Models/TorrentPotatoRequest.cs b/src/Jackett/Models/TorrentPotatoRequest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4581bcaa067c51e8857a2fff3efbab54fa479743
--- /dev/null
+++ b/src/Jackett/Models/TorrentPotatoRequest.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models
+{
+    public class TorrentPotatoRequest
+    {
+        public string username { get; set; }
+        public string passkey { get; set; }
+        public string imdbid { get; set; }
+        public string search { get; set; }
+    }
+}
diff --git a/src/Jackett/Models/TorrentPotatoResponse.cs b/src/Jackett/Models/TorrentPotatoResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ea76ff6acb3eeff8f0f09973b25719d4aa0de667
--- /dev/null
+++ b/src/Jackett/Models/TorrentPotatoResponse.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models
+{
+   public class TorrentPotatoResponse
+    {
+        public TorrentPotatoResponse()
+        {
+            results = new List<TorrentPotatoResponseItem>();
+        }
+        public List<TorrentPotatoResponseItem> results { get; set; }
+
+        public int total_results
+        {
+            get { return results.Count; }
+        }
+    }
+}
diff --git a/src/Jackett/Models/TorrentPotatoResponseItem.cs b/src/Jackett/Models/TorrentPotatoResponseItem.cs
new file mode 100644
index 0000000000000000000000000000000000000000..809ada2b68bde9cc6ce2f8d087d34c84f9ad2568
--- /dev/null
+++ b/src/Jackett/Models/TorrentPotatoResponseItem.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models
+{
+    public class TorrentPotatoResponseItem
+    {
+        public string release_name { get; set; }
+        public string torrent_id { get; set; }
+        public string details_url { get; set; }
+        public string download_url { get; set; }
+       // public string imdb_id { get; set; }
+        public bool freeleech { get; set; }
+        public string type { get; set; }
+        public long size { get; set; }
+        public int leechers { get; set; }
+        public int seeders { get; set; }
+    }
+}
diff --git a/src/Jackett/Models/TorznabCapabilities.cs b/src/Jackett/Models/TorznabCapabilities.cs
index 012ad8e5702f202c5ac7b897782c4feced49f6b0..f85f2275d415add33ccec46853ea09b5dfc09522 100644
--- a/src/Jackett/Models/TorznabCapabilities.cs
+++ b/src/Jackett/Models/TorznabCapabilities.cs
@@ -1,4 +1,5 @@
-using System;
+using Newtonsoft.Json.Linq;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -21,6 +22,18 @@ namespace Jackett.Models
         public TorznabCapabilities()
         {
             Categories = new List<TorznabCategory>();
+            SearchAvailable = true;
+            TVSearchAvailable = true;
+            SupportsTVRageSearch = false;
+        }
+
+        public TorznabCapabilities(params TorznabCategory[] cats)
+        {
+            SearchAvailable = true;
+            TVSearchAvailable = true;
+            SupportsTVRageSearch = false;
+            Categories = new List<TorznabCategory>();
+            Categories.AddRange(cats);
         }
 
         string SupportedTVSearchParams
@@ -34,6 +47,16 @@ namespace Jackett.Models
             }
         }
 
+        public JArray CapsToJson()
+        {
+            var jArray = new JArray();
+            foreach (var cat in Categories.GroupBy(p => p.ID).Select(g => g.First()).OrderBy(c=>c.ID))
+            {
+                jArray.Add(cat.ToJson());
+            }
+            return jArray;
+        }
+
         public string ToXml()
         {
             var xdoc = new XDocument(
diff --git a/src/Jackett/Models/TorznabCatType.cs b/src/Jackett/Models/TorznabCatType.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f78fb39950b6948f57a2f6c01359570629aafea0
--- /dev/null
+++ b/src/Jackett/Models/TorznabCatType.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models
+{
+    class TorznabCatType
+    {
+        private static Dictionary<int, string> cats = new Dictionary<int, string>();
+
+        static TorznabCatType()
+        {
+            cats.Add(5000, "TV");
+            cats.Add(5030, "TV/SD");
+            cats.Add(5040, "TV/HD");
+            cats.Add(5070, "TV/Anime");
+            cats.Add(8000, "Books");
+            cats.Add(8020, "Books/Comics");
+            cats.Add(4000, "PC");
+            cats.Add(3030, "Audio/Audiobook");
+            cats.Add(2000, "Movies");
+            cats.Add(2040, "Movies/HD");
+            cats.Add(2030, "Movies/SD");
+            cats.Add(2010, "Movies/Foreign");
+            cats.Add(3000, "Audio");
+            cats.Add(3040, "Audio/Lossless");
+            cats.Add(3010, "Audio/MP3");
+            cats.Add(6000, "XXX");
+            cats.Add(6040, "XXX/x264");
+            cats.Add(6010, "XXX/DVD");
+            cats.Add(6060, "XXX/Imageset");
+        }
+
+        public static bool QueryContainsParentCategory(int[] queryCats, int releaseCat)
+        {
+            if (cats.ContainsKey(releaseCat) && queryCats!=null)
+            {
+                var ncab = cats[releaseCat];
+                var split = ncab.IndexOf("/");
+                if (split > -1)
+                {
+                    string parentCatName = ncab.Substring(0,split);
+                    if (cats.ContainsValue(parentCatName))
+                    {
+                        var parentCat = cats.Where(c => c.Value == parentCatName).First().Key;
+                        return queryCats.Contains(parentCat);
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        public static string GetCatDesc(int newznabcat)
+        {
+            if (cats.ContainsKey(newznabcat))
+            {
+                return cats[newznabcat];
+            }
+
+            return string.Empty;
+        }
+
+        private static TorznabCategory GetCat(int id)
+        {
+            return new TorznabCategory()
+            {
+                ID = id,
+                Name = cats[id]
+            };
+        }
+
+        public static TorznabCategory Anime
+        {
+            get { return GetCat(5070); }
+        }
+
+        public static TorznabCategory TV
+        {
+            get { return GetCat(5000); }
+        }
+
+        public static TorznabCategory TVSD
+        {
+            get { return GetCat(5030); }
+        }
+
+        public static TorznabCategory TVHD
+        {
+            get { return GetCat(5040); }
+        }
+
+        public static TorznabCategory Books
+        {
+            get { return GetCat(8000); }
+        }
+
+        public static TorznabCategory Comic
+        {
+            get { return GetCat(8020); }
+        }
+
+        public static TorznabCategory Apps
+        {
+            get { return GetCat(4000); }
+        }
+
+        public static TorznabCategory AudioBooks
+        {
+            get { return GetCat(3030); }
+        }
+
+        public static TorznabCategory Movies
+        {
+            get { return GetCat(2000); }
+        }
+
+        public static TorznabCategory MoviesHD
+        {
+            get { return GetCat(2040); }
+        }
+
+        public static TorznabCategory MoviesSD
+        {
+            get { return GetCat(2030); }
+        }
+
+        public static TorznabCategory MoviesForeign
+        {
+            get { return GetCat(2040); }
+        }
+
+        public static TorznabCategory Audio
+        {
+            get { return GetCat(3000); }
+        }
+
+        public static TorznabCategory AudioLossless
+        {
+            get { return GetCat(3040); }
+        }
+
+        public static TorznabCategory AudioLossy
+        {
+            get { return GetCat(3010); }
+        }
+
+        public static TorznabCategory XXX
+        {
+            get { return GetCat(6000); }
+        }
+
+        public static TorznabCategory XXXHD
+        {
+            get { return GetCat(6040); }
+        }
+
+        public static TorznabCategory XXXSD
+        {
+            get { return GetCat(6010); }
+        }
+
+        public static TorznabCategory XXXImg
+        {
+            get { return GetCat(6060); }
+        }
+    }
+}
diff --git a/src/Jackett/Models/TorznabCategory.cs b/src/Jackett/Models/TorznabCategory.cs
index 53425c540a97e43e18b4babbae48a3545db2b33e..2e9e939f9d998c429ec70c51ba480a5d2aad1ab4 100644
--- a/src/Jackett/Models/TorznabCategory.cs
+++ b/src/Jackett/Models/TorznabCategory.cs
@@ -1,4 +1,5 @@
-using System;
+using Newtonsoft.Json.Linq;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -8,7 +9,7 @@ namespace Jackett.Models
 {
     public class TorznabCategory
     {
-        public string ID { get; set; }
+        public int ID { get; set; }
         public string Name { get; set; }
 
         public List<TorznabCategory> SubCategories { get; private set; }
@@ -17,5 +18,13 @@ namespace Jackett.Models
         {
             SubCategories = new List<TorznabCategory>();
         }
+
+        public JToken ToJson()
+        {
+            var t = new JObject();
+            t["ID"] = ID;
+            t["Name"] = Name;
+            return t;
+        }
     }
 }
diff --git a/src/Jackett/Models/TorznabQuery.cs b/src/Jackett/Models/TorznabQuery.cs
index d27698517a711ab0f3dc5742a484e0c35982e844..b3bd85782b6cfc266a6031c0e216bad9aca4d9e8 100644
--- a/src/Jackett/Models/TorznabQuery.cs
+++ b/src/Jackett/Models/TorznabQuery.cs
@@ -12,7 +12,7 @@ namespace Jackett.Models
     public class TorznabQuery
     {
         public string QueryType { get; set; }
-        public string[] Categories { get; set; }
+        public int[] Categories { get; set; }
         public int Extended { get; set; }
         public string ApiKey { get; set; }
         public int Limit { get; set; }
@@ -22,7 +22,30 @@ namespace Jackett.Models
         public int Season { get; set; }
         public string Episode { get; set; }
         public string SearchTerm { get; set; }
-        public string SanitizedSearchTerm { get; set; }
+
+        public string SanitizedSearchTerm
+        {
+            get
+            {
+                if (SearchTerm == null)
+                    return string.Empty;
+
+                char[] arr = SearchTerm.ToCharArray();
+
+                arr = Array.FindAll<char>(arr, c => (char.IsLetterOrDigit(c)
+                                                  || char.IsWhiteSpace(c)
+                                                  || c == '-'
+                                                  || c == '.'
+                                                  ));
+                var safetitle = new string(arr);
+                return safetitle;
+            }
+        }
+
+        public TorznabQuery()
+        {
+            Categories = new int[0];
+        }
 
         public string GetEpisodeSearchString()
         {
@@ -41,19 +64,6 @@ namespace Jackett.Models
             return episodeString;
         }
 
-        static string SanitizeSearchTerm(string title)
-        {
-            char[] arr = title.ToCharArray();
-
-            arr = Array.FindAll<char>(arr, c => (char.IsLetterOrDigit(c)
-                                              || char.IsWhiteSpace(c)
-                                              || c == '-'
-                                              || c == '.'
-                                              ));
-            title = new string(arr);
-            return title;
-        }
-
         public static TorznabQuery FromHttpQuery(NameValueCollection query)
         {
 
@@ -64,17 +74,18 @@ namespace Jackett.Models
             if (query["q"] == null)
             {
                 q.SearchTerm = string.Empty;
-                q.SanitizedSearchTerm = string.Empty;
             }
             else
             {
                 q.SearchTerm = query["q"];
-                q.SanitizedSearchTerm = SanitizeSearchTerm(q.SearchTerm);
             }
 
             if (query["cat"] != null)
             {
-                q.Categories = query["cat"].Split(',');
+                q.Categories = query["cat"].Split(',').Select(s => int.Parse(s)).ToArray();
+            }else
+            {
+                q.Categories = new int[0];
             }
 
             if (query["extended"] != null)
diff --git a/src/Jackett/Models/TrackerCacheResult.cs b/src/Jackett/Models/TrackerCacheResult.cs
index 0ea9af6501a37351e24acaf5f2d690c6c46952e5..13b1569e8fbe2297277eada265cc529de066c748 100644
--- a/src/Jackett/Models/TrackerCacheResult.cs
+++ b/src/Jackett/Models/TrackerCacheResult.cs
@@ -10,5 +10,6 @@ namespace Jackett.Models
     {
         public DateTime FirstSeen { get; set; }
         public string Tracker { get; set; }
+        public string CategoryDesc { get; set; }
     }
 }
diff --git a/src/Jackett/Properties/AssemblyInfo.cs b/src/Jackett/Properties/AssemblyInfo.cs
index c854b4d9e8daff5e35a21cf29c2c763120599831..bd946bcd97976dab5723be2785996673b6033c1e 100644
--- a/src/Jackett/Properties/AssemblyInfo.cs
+++ b/src/Jackett/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers 
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.5.1.0")]
+[assembly: AssemblyVersion("0.6.0.0")]
 [assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Jackett/Properties/Resources.Designer.cs b/src/Jackett/Properties/Resources.Designer.cs
index b35923b6e50f0ce11bbde08b95ace025d6db827b..b9898d2b78b1b004fc3b00840c0f03641da9ffa3 100644
--- a/src/Jackett/Properties/Resources.Designer.cs
+++ b/src/Jackett/Properties/Resources.Designer.cs
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // <auto-generated>
 //     This code was generated by a tool.
-//     Runtime Version:4.0.30319.34209
+//     Runtime Version:4.0.30319.42000
 //
 //     Changes to this file may cause incorrect behavior and will be lost if
 //     the code is regenerated.
@@ -72,7 +72,7 @@ namespace Jackett.Properties {
         ///    &lt;webMaster&gt;($email) (HDA Invites)&lt;/webMaster&gt;
         ///    &lt;category&gt;search&lt;/category&gt;
         ///    &lt;image&gt;
-        ///      &lt;url&gt;https://hdac [rest of string was truncated]&quot;;.
+        ///      &lt;url&gt;h [rest of string was truncated]&quot;;.
         /// </summary>
         internal static string test {
             get {
diff --git a/src/Jackett/Services/CacheService.cs b/src/Jackett/Services/CacheService.cs
index 400649f389aac52091be78c4be764a9e045ac631..84548bf378d0fa69045c01f5f29621c30c97c9c2 100644
--- a/src/Jackett/Services/CacheService.cs
+++ b/src/Jackett/Services/CacheService.cs
@@ -10,22 +10,17 @@ namespace Jackett.Services
 {
     public interface ICacheService
     {
-        void CacheRssResults(string trackerId, ReleaseInfo[] releases);
+        void CacheRssResults(string trackerId, IEnumerable<ReleaseInfo> releases);
         List<TrackerCacheResult> GetCachedResults();
     }
 
     public class CacheService : ICacheService
     {
         private readonly List<TrackerCache> cache = new List<TrackerCache>();
-        private readonly int MAX_RESULTS_PER_TRACKER = 100;
+        private readonly int MAX_RESULTS_PER_TRACKER = 250;
         private readonly TimeSpan AGE_LIMIT = new TimeSpan(2, 0, 0, 0);
 
-        static CacheService()
-        {
-            Mapper.CreateMap<ReleaseInfo,TrackerCacheResult>();
-        }
-
-        public void CacheRssResults(string trackerId, ReleaseInfo[] releases)
+        public void CacheRssResults(string trackerId, IEnumerable<ReleaseInfo> releases)
         {
             lock (cache)
             {
diff --git a/src/Jackett/Services/IndexerManagerService.cs b/src/Jackett/Services/IndexerManagerService.cs
index 47113a7413aeb695c3bcd063732e197767ca0b53..f95c44a52e0592e6e7165c8947c14425940f6cf8 100644
--- a/src/Jackett/Services/IndexerManagerService.cs
+++ b/src/Jackett/Services/IndexerManagerService.cs
@@ -2,6 +2,7 @@
 using Jackett.Indexers;
 using Jackett.Models;
 using Jackett.Utils;
+using Jackett.Utils.Clients;
 using Newtonsoft.Json.Linq;
 using NLog;
 using System;
@@ -29,16 +30,20 @@ namespace Jackett.Services
         private IConfigurationService configService;
         private Logger logger;
         private Dictionary<string, IIndexer> indexers = new Dictionary<string, IIndexer>();
+        private ICacheService cacheService;
 
-        public IndexerManagerService(IContainer c, IConfigurationService config, Logger l)
+        public IndexerManagerService(IContainer c, IConfigurationService config, Logger l, ICacheService cache)
         {
             container = c;
             configService = config;
             logger = l;
+            cacheService = cache;
         }
 
         public void InitIndexers()
         {
+            logger.Info("Using HTTP Client: " + container.Resolve<IWebClient>().GetType().Name);
+
             foreach (var idx in container.Resolve<IEnumerable<IIndexer>>().OrderBy(_ => _.DisplayName))
             {
                 indexers.Add(idx.ID, idx);
@@ -74,9 +79,10 @@ namespace Jackett.Services
             var indexer = GetIndexer(name);
             var browseQuery = new TorznabQuery();
             var results = await indexer.PerformQuery(browseQuery);
-            logger.Info(string.Format("Found {0} releases from {1}", results.Length, indexer.DisplayName));
-            if (results.Length == 0)
+            logger.Info(string.Format("Found {0} releases from {1}", results.Count(), indexer.DisplayName));
+            if (results.Count() == 0)
                 throw new Exception("Found no results while trying to browse this tracker");
+            cacheService.CacheRssResults(indexer.DisplayName, results);
         }
 
         public void DeleteIndexer(string name)
diff --git a/src/Jackett/Services/ServerService.cs b/src/Jackett/Services/ServerService.cs
index ba97771782ff57c856b51dbc968e718057abec3b..cdd82b782b1ee25365fb00ce1afb1255bd41c2db 100644
--- a/src/Jackett/Services/ServerService.cs
+++ b/src/Jackett/Services/ServerService.cs
@@ -1,6 +1,7 @@
 using Autofac;
 using Jackett.Models.Config;
 using Jackett.Services;
+using Jackett.Utils.Clients;
 using Microsoft.Owin.Hosting;
 using Newtonsoft.Json.Linq;
 using NLog;
@@ -41,14 +42,16 @@ namespace Jackett.Services
         private ISerializeService serializeService;
         private IConfigurationService configService;
         private Logger logger;
+        private IWebClient client;
 
-        public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l)
+        public ServerService(IIndexerManagerService i, IProcessService p, ISerializeService s, IConfigurationService c, Logger l, IWebClient w)
         {
             indexerService = i;
             processService = p;
             serializeService = s;
             configService = c;
             logger = l;
+            client = w;
 
             LoadConfig();
         }
@@ -105,12 +108,9 @@ namespace Jackett.Services
         {
             logger.Info("Starting Jackett " + configService.GetVersion());
             CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
-
-            // Allow all SSL.. sucks I know but mono on linux is having problems without it..
-            ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
-
             // Load indexers
             indexerService.InitIndexers();
+            client.Init();
         }
 
         public void Start()
diff --git a/src/Jackett/Startup.cs b/src/Jackett/Startup.cs
index 779d49d51f9d9ea70121570f085b155f33de3ed5..d427afd4a2a02e46e96665bcf0fa5a58b8645f2d 100644
--- a/src/Jackett/Startup.cs
+++ b/src/Jackett/Startup.cs
@@ -34,7 +34,13 @@ namespace Jackett
             set;
         }
 
-        public static bool CurlSafe
+        public static string ClientOverride
+        {
+            get;
+            set;
+        }
+
+        public static bool? DoSSLFix
         {
             get;
             set;
@@ -70,18 +76,42 @@ namespace Jackett
             config.Routes.MapHttpRoute(
                 name: "apiDefault",
                 routeTemplate: "api/{indexerID}",
-                defaults: new { controller = "API", action = "Call" }
+                defaults: new { controller = "Torznab", action = "Call" }
             );
 
             config.Routes.MapHttpRoute(
                name: "api",
                routeTemplate: "api/{indexerID}/api",
-               defaults: new { controller = "API", action = "Call" }
+               defaults: new { controller = "Torznab", action = "Call" }
+           );
+
+            config.Routes.MapHttpRoute(
+               name: "torznabDefault",
+               routeTemplate: "torznab/{indexerID}",
+               defaults: new { controller = "Torznab", action = "Call" }
+           );
+
+            config.Routes.MapHttpRoute(
+               name: "torznab",
+               routeTemplate: "torznab/{indexerID}/api",
+               defaults: new { controller = "Torznab", action = "Call" }
+           );
+
+            config.Routes.MapHttpRoute(
+              name: "potatoDefault",
+              routeTemplate: "potato/{indexerID}",
+              defaults: new { controller = "Potato", action = "Call" }
+          );
+
+            config.Routes.MapHttpRoute(
+               name: "potato",
+               routeTemplate: "potato/{indexerID}/api",
+               defaults: new { controller = "Potato", action = "Call" }
            );
 
             config.Routes.MapHttpRoute(
                 name: "download",
-                routeTemplate: "api/{indexerID}/download/{path}/download.torrent",
+                routeTemplate: "api/{indexerID}/download/{path}/t.torrent",
                 defaults: new { controller = "Download", action = "Download" }
             );
 
diff --git a/src/Jackett/Utils/Clients/BaseWebResult.cs b/src/Jackett/Utils/Clients/BaseWebResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..699751c22465d139ebf95ee3060482c1a54817a6
--- /dev/null
+++ b/src/Jackett/Utils/Clients/BaseWebResult.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Utils.Clients
+{
+    public abstract class BaseWebResult
+    {
+        public HttpStatusCode Status { get; set; }
+        public string Cookies { get; set; }
+        public string RedirectingTo { get; set; }
+
+        public bool IsRedirect
+        {
+          get
+            {
+             return  Status == System.Net.HttpStatusCode.Redirect ||
+                     Status == System.Net.HttpStatusCode.RedirectKeepVerb ||
+                     Status == System.Net.HttpStatusCode.RedirectMethod ||
+                     Status == System.Net.HttpStatusCode.Found ||
+                     Status == System.Net.HttpStatusCode.MovedPermanently;
+            }
+        }
+    }
+}
diff --git a/src/Jackett/Utils/Clients/WindowsWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs
similarity index 54%
rename from src/Jackett/Utils/Clients/WindowsWebClient.cs
rename to src/Jackett/Utils/Clients/HttpWebClient.cs
index 12048fe0556f11269569502f5c60f05a2a519c86..92a88b99f481eac0d0bc5649204d7714cc7c9977 100644
--- a/src/Jackett/Utils/Clients/WindowsWebClient.cs
+++ b/src/Jackett/Utils/Clients/HttpWebClient.cs
@@ -1,4 +1,5 @@
-using Jackett.Models;
+using AutoMapper;
+using Jackett.Models;
 using NLog;
 using System;
 using System.Collections.Generic;
@@ -10,21 +11,38 @@ using System.Threading.Tasks;
 
 namespace Jackett.Utils.Clients
 {
-    class WindowsWebClient : IWebClient
+    class HttpWebClient : IWebClient
     {
         private Logger logger;
-      
 
-        public WindowsWebClient(Logger l)
+        public HttpWebClient(Logger l)
         {
             logger = l;
-          
+        }
+
+
+        public void Init()
+        {
         }
 
         public async Task<WebClientByteResult> GetBytes(WebRequest request)
         {
             logger.Debug(string.Format("WindowsWebClient:GetBytes(Url:{0})", request.Url));
+            var result = await Run(request);
+            logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "<NULL>" : result.Content.Length.ToString())));
+            return result;
+        }
+
+        public async Task<WebClientStringResult> GetString(WebRequest request)
+        {
+            logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url));
+            var result = await Run(request);
+            logger.Debug(string.Format("WindowsWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "<NULL>" : Encoding.UTF8.GetString(result.Content))));
+            return Mapper.Map<WebClientStringResult>(result);
+        }
 
+        private async Task<WebClientByteResult> Run(WebRequest request)
+        {
             var cookies = new CookieContainer();
             if (!string.IsNullOrEmpty(request.Cookies))
             {
@@ -51,7 +69,7 @@ namespace Jackett.Utils.Clients
 
             client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
             HttpResponseMessage response = null;
-
+           
             if (request.Type == RequestType.POST)
             {
                 var content = new FormUrlEncodedContent(request.PostData);
@@ -64,69 +82,11 @@ namespace Jackett.Utils.Clients
 
             var result = new WebClientByteResult();
             result.Content = await response.Content.ReadAsByteArrayAsync();
-           
-            result.Status = response.StatusCode;
-
-            // Compatiblity issue between the cookie format and httpclient
-            // Pull it out manually ignoring the expiry date then set it manually
-            // http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer
-            IEnumerable<string> cookieHeaders;
-            if (response.Headers.TryGetValues("set-cookie", out cookieHeaders))
-            {
-                var cookieBuilder = new StringBuilder();
-                foreach (var c in cookieHeaders)
-                {
-                    cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.LastIndexOf(';')));
-                }
-
-                result.Cookies = cookieBuilder.ToString().TrimEnd();
-            }
-
-            return result;
-        }
-
-        public async Task<WebClientStringResult> GetString(WebRequest request)
-        {
-            logger.Debug(string.Format("WindowsWebClient:GetString(Url:{0})", request.Url));
-            var cookies = new CookieContainer();
-
-            if (!string.IsNullOrEmpty(request.Cookies))
+            if (response.Headers.Location != null)
             {
-                var uri = new Uri(request.Url);
-                foreach (var c in request.Cookies.Split(';'))
-                {
-                    try
-                    {
-                        cookies.SetCookies(uri, c);
-                    }
-                    catch (CookieException ex)
-                    {
-                        logger.Info("(Non-critical) Problem loading cookie {0}, {1}, {2}", uri, c, ex.Message);
-                    }
-                }
-            }
-
-            var client = new HttpClient(new HttpClientHandler
-            {
-                CookieContainer = cookies,
-                AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more.
-                UseCookies = true,
-            });
-
-            client.DefaultRequestHeaders.Add("User-Agent", BrowserUtil.ChromeUserAgent);
-            HttpResponseMessage response = null;
-
-            if (request.Type == RequestType.POST)
-            {
-                var content = new FormUrlEncodedContent(request.PostData);
-                response = await client.PostAsync(request.Url, content);
-            } else
-            {
-                response = await client.GetAsync(request.Url);
+                result.RedirectingTo = response.Headers.Location.ToString();
             }
-
-            var result = new WebClientStringResult();
-            result.Content = await response.Content.ReadAsStringAsync();
+            result.Status = response.StatusCode;
 
             // Compatiblity issue between the cookie format and httpclient
             // Pull it out manually ignoring the expiry date then set it manually
@@ -137,22 +97,13 @@ namespace Jackett.Utils.Clients
                 var cookieBuilder = new StringBuilder();
                 foreach (var c in cookieHeaders)
                 {
-                    if (cookieBuilder.Length > 0)
-                    {
-                        cookieBuilder.Append("; ");
-                    }
-
-                    cookieBuilder.Append( c.Substring(0, c.IndexOf(';')));
+                    cookieBuilder.AppendFormat("{0} ", c.Substring(0, c.IndexOf(';')+1));
                 }
 
-                result.Cookies = cookieBuilder.ToString();
+                result.Cookies = cookieBuilder.ToString().TrimEnd();
             }
 
-            result.Status = response.StatusCode;
-            if (null != response.Headers.Location)
-            {
-                result.RedirectingTo = response.Headers.Location.ToString();
-            }
+            ServerUtil.ResureRedirectIsFullyQualified(request, result);
             return result;
         }
     }
diff --git a/src/Jackett/Utils/Clients/IWebClient.cs b/src/Jackett/Utils/Clients/IWebClient.cs
index b7d3e1947734fdd86da4a8af2154a217d98a7acc..24ad8dfcb4d5071755654ece6ef1d090edc90d70 100644
--- a/src/Jackett/Utils/Clients/IWebClient.cs
+++ b/src/Jackett/Utils/Clients/IWebClient.cs
@@ -11,5 +11,6 @@ namespace Jackett.Utils.Clients
     {
         Task<WebClientStringResult> GetString(WebRequest request);
         Task<WebClientByteResult> GetBytes(WebRequest request);
+        void Init();
     }
 }
diff --git a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
index db04057881ba1f58bbe58e745a47d008b20a6c09..58f2e024f9e0b0eafbcae896918b8c96ac0f683b 100644
--- a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
+++ b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
@@ -1,4 +1,6 @@
-using Jackett.Models;
+using AutoMapper;
+using CurlSharp;
+using Jackett.Models;
 using Jackett.Services;
 using NLog;
 using System;
@@ -23,54 +25,86 @@ namespace Jackett.Utils.Clients
 
         public async Task<WebClientByteResult> GetBytes(WebRequest request)
         {
-            Jackett.CurlHelper.CurlResponse response;
-
             logger.Debug(string.Format("UnixLibCurlWebClient:GetBytes(Url:{0})", request.Url));
+            var result = await Run(request);
+            logger.Debug(string.Format("UnixLibCurlWebClient:GetBytes Returning {0} => {1} bytes", result.Status, (result.Content==null?"<NULL>":result.Content.Length.ToString())));
+            return result;
+        }
+
+        public async Task<WebClientStringResult> GetString(WebRequest request)
+        {
+            logger.Debug(string.Format("UnixLibCurlWebClient:GetString(Url:{0})", request.Url));
+            var result = await Run(request);
+            logger.Debug(string.Format("UnixLibCurlWebClient:GetString Returning {0} => {1}", result.Status, (result.Content== null?"<NULL>": Encoding.UTF8.GetString(result.Content))));
+            return Mapper.Map<WebClientStringResult>(result);
+        }
 
+        public void Init()
+        {
+            try {
+                Engine.Logger.Info("LibCurl init " + Curl.GlobalInit(CurlInitFlag.All).ToString());
+                CurlHelper.OnErrorMessage += (msg) =>
+                 {
+                     Engine.Logger.Error(msg);
+                 };
+            }
+            catch(Exception e)
+            {
+                Engine.Logger.Warn("Libcurl failed to initalize. Did you install it?");
+                Engine.Logger.Warn("Debian: apt-get install libcurl4-openssl-dev");
+                Engine.Logger.Warn("Redhat: yum install libcurl-devel");
+                throw e;
+            }
+
+            var version = Curl.Version;
+            Engine.Logger.Info("LibCurl version " + version);
+
+            if (!Startup.DoSSLFix.HasValue && version.IndexOf("NSS")>-1)
+            {
+                Engine.Logger.Info("NSS Detected SSL ECC workaround enabled.");
+                Startup.DoSSLFix = true;
+            }
+        }
+
+        private async Task<WebClientByteResult> Run(WebRequest request)
+        {
+            Jackett.CurlHelper.CurlResponse response;
             if (request.Type == RequestType.GET)
             {
                 response = await CurlHelper.GetAsync(request.Url, request.Cookies, request.Referer);
             }
             else
             {
+                if (request.PostData != null && request.PostData.Count > 0)
+                {
+                    logger.Debug("UnixLibCurlWebClient: Posting " + new FormUrlEncodedContent(request.PostData).ReadAsStringAsync().Result);
+                }
+
                 response = await CurlHelper.PostAsync(request.Url, request.PostData, request.Cookies, request.Referer);
             }
 
             var result = new WebClientByteResult()
             {
                 Content = response.Content,
-                Cookies = response.CookieHeader,
+                Cookies = response.Cookies,
                 Status = response.Status
             };
 
-            if (response.Headers != null)
+            if (response.HeaderList != null)
             {
-                foreach(var header in response.Headers)
+                foreach (var header in response.HeaderList)
                 {
-                    if(string.Equals(header.Key, "location", StringComparison.InvariantCultureIgnoreCase) && header.Value !=null)
+                    switch (header[0].ToLowerInvariant())
                     {
-                        result.RedirectingTo = header.Value;
+                        case "location":
+                            result.RedirectingTo = header[1];
+                            break;
                     }
                 }
             }
 
-            logger.Debug(string.Format("UnixLibCurlWebClient: Returning", result.Status));
+            ServerUtil.ResureRedirectIsFullyQualified(request, result);
             return result;
         }
-
-        public async Task<WebClientStringResult> GetString(WebRequest request)
-        {
-            logger.Debug(string.Format("UnixLibCurlWebClient:GetString(Url:{0})", request.Url));
-            var result = await GetBytes(request);
-
-            var sresult = new WebClientStringResult()
-            {
-                Content = Encoding.UTF8.GetString(result.Content),
-                Cookies = result.Cookies,
-                Status = result.Status
-            };
-
-            return sresult;
-        }
     }
 }
diff --git a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
index f0822950844127a888ff08a1320d163bde864ee9..7f3d8fe474639c4d2d1284b33e564c754ece0fd0 100644
--- a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
+++ b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
@@ -1,4 +1,6 @@
-using Jackett.Models;
+using AutoMapper;
+using CurlSharp;
+using Jackett.Models;
 using Jackett.Services;
 using NLog;
 using System;
@@ -23,30 +25,30 @@ namespace Jackett.Utils.Clients
             logger = l;
         }
 
-        public Task<WebClientByteResult> GetBytes(WebRequest request)
+        public void Init()
+        {
+        }
+
+        public async Task<WebClientByteResult> GetBytes(WebRequest request)
         {
             logger.Debug(string.Format("UnixSafeCurlWebClient:GetBytes(Url:{0})", request.Url));
-            return Run(request);
+            var result = await Run(request);
+            logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1} bytes", result.Status, (result.Content == null ? "<NULL>" : result.Content.Length.ToString())));
+            return result;
         }
 
         public async Task<WebClientStringResult> GetString(WebRequest request)
         {
             logger.Debug(string.Format("UnixSafeCurlWebClient:GetString(Url:{0})", request.Url));
-            var byteResult = await Run(request);
-            return new WebClientStringResult()
-            {
-                Cookies = byteResult.Cookies,
-                Status = byteResult.Status,
-                Content = Encoding.UTF8.GetString(byteResult.Content),
-                RedirectingTo = byteResult.RedirectingTo
-            };
+            var result = await Run(request);
+            logger.Debug(string.Format("UnixSafeCurlWebClient: Returning {0} => {1}", result.Status, (result.Content == null ? "<NULL>" : Encoding.UTF8.GetString(result.Content))));
+            return Mapper.Map<WebClientStringResult>(result);
         }
 
         private async Task<WebClientByteResult> Run(WebRequest request)
         {
             var args = new StringBuilder();
             args.AppendFormat("--url \"{0}\" ", request.Url);
-
             args.AppendFormat("-i  -sS --user-agent \"{0}\" ", BrowserUtil.ChromeUserAgent);
 
             if (!string.IsNullOrWhiteSpace(request.Cookies))
@@ -66,9 +68,15 @@ namespace Jackett.Utils.Clients
             }
 
             var tempFile = Path.GetTempFileName();
-
             args.AppendFormat("--output \"{0}\" ", tempFile);
 
+            if (Startup.DoSSLFix == true)
+            {
+                // http://stackoverflow.com/questions/31107851/how-to-fix-curl-35-cannot-communicate-securely-with-peer-no-common-encryptio
+                // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html
+                args.Append("--cipher " + SSLFix.CipherList);
+            }
+
             string stdout = null;
             await Task.Run(() =>
             {
@@ -77,9 +85,7 @@ namespace Jackett.Utils.Clients
 
             var outputData = File.ReadAllBytes(tempFile);
             File.Delete(tempFile);
-
             stdout = Encoding.UTF8.GetString(outputData);
-
             var result = new WebClientByteResult();
             var headSplit = stdout.IndexOf("\r\n\r\n");
             if (headSplit < 0)
@@ -127,6 +133,7 @@ namespace Jackett.Utils.Clients
             }
 
             logger.Debug("WebClientByteResult returned " + result.Status);
+            ServerUtil.ResureRedirectIsFullyQualified(request, result);
             return result;
         }
     }
diff --git a/src/Jackett/Utils/Clients/WebByteResult.cs b/src/Jackett/Utils/Clients/WebByteResult.cs
index b904ec0705ea542e6511ec2cbf3bf09f9d87224e..f0ae9de900f72ab1f833797074867a6c4cac01aa 100644
--- a/src/Jackett/Utils/Clients/WebByteResult.cs
+++ b/src/Jackett/Utils/Clients/WebByteResult.cs
@@ -7,11 +7,8 @@ using System.Threading.Tasks;
 
 namespace Jackett.Utils.Clients
 {
-    public class WebClientByteResult
+    public class WebClientByteResult : BaseWebResult
     {
-        public HttpStatusCode Status { get; set; }
-        public string Cookies { get; set; }
         public byte[] Content { get; set; }
-        public string RedirectingTo { get; set; }
     }
 }
diff --git a/src/Jackett/Utils/Clients/WebClientResult.cs b/src/Jackett/Utils/Clients/WebClientResult.cs
index a801e880081f019e5f251974fa7ac0809e371241..2898e05ebc6a26c1c8c2101bd5360e245f20a463 100644
--- a/src/Jackett/Utils/Clients/WebClientResult.cs
+++ b/src/Jackett/Utils/Clients/WebClientResult.cs
@@ -7,11 +7,8 @@ using System.Threading.Tasks;
 
 namespace Jackett.Utils.Clients
 {
-    public class WebClientStringResult
+    public class WebClientStringResult:  BaseWebResult
     {
-        public HttpStatusCode Status { get; set; }
-        public string Cookies { get; set; }
         public string Content { get; set; }
-        public string RedirectingTo { get; set; }
     }
 }
diff --git a/src/Jackett/Utils/Clients/WebRequest.cs b/src/Jackett/Utils/Clients/WebRequest.cs
index 5dd60ea9b2fc533f26a55543d637b46b1cef0ed1..78fc2f4638b816eb99b4f52d553dcdeaac8be85f 100644
--- a/src/Jackett/Utils/Clients/WebRequest.cs
+++ b/src/Jackett/Utils/Clients/WebRequest.cs
@@ -14,6 +14,13 @@ namespace Jackett.Utils.Clients
             Type = RequestType.GET;
         }
 
+        public WebRequest(string url)
+        {
+            PostData = new Dictionary<string, string>();
+            Type = RequestType.GET;
+            Url = url;
+        }
+
         public string Url { get; set; }
         public Dictionary<string, string> PostData { get; set; }
         public string Cookies { get; set; }
diff --git a/src/Jackett/Utils/JsonContent.cs b/src/Jackett/Utils/JsonContent.cs
new file mode 100644
index 0000000000000000000000000000000000000000..412962bda23b5dfe6001ef7b7faad48f637b7a37
--- /dev/null
+++ b/src/Jackett/Utils/JsonContent.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Utils
+{
+    public class JsonContent : HttpContent
+    {
+        private readonly object _value;
+
+        public JsonContent(object value)
+        {
+            _value = value;
+            Headers.ContentType = new MediaTypeHeaderValue("application/json");
+        }
+
+        protected override async Task SerializeToStreamAsync(Stream stream,
+            TransportContext context)
+        {
+            var json = JsonConvert.SerializeObject(_value, Formatting.Indented);
+            var writer = new StreamWriter(stream);
+            writer.Write(json);
+            await writer.FlushAsync();
+        }
+
+        protected override bool TryComputeLength(out long length)
+        {
+            length = -1;
+            return false;
+        }
+    }
+}
diff --git a/src/Jackett/Utils/ServerUtil.cs b/src/Jackett/Utils/ServerUtil.cs
index 6e3f16d320c82f0a7f554a8185d25a41addf256c..a176e810fd4c720e6dc381051c243b7051a0caa0 100644
--- a/src/Jackett/Utils/ServerUtil.cs
+++ b/src/Jackett/Utils/ServerUtil.cs
@@ -1,4 +1,5 @@
-using System;
+using Jackett.Utils.Clients;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Security.Principal;
@@ -98,5 +99,19 @@ namespace Jackett.Utils
             }
             return isAdmin;
         }
+
+        public static void ResureRedirectIsFullyQualified(WebRequest req, BaseWebResult result)
+        {
+            if (!string.IsNullOrEmpty(result.RedirectingTo))
+            {
+                var destLower = result.RedirectingTo.ToLowerInvariant();
+                if (!destLower.StartsWith("http"))
+                {
+                    var hostUri = new Uri(req.Url);
+                    var fullUri = new Uri(hostUri, result.RedirectingTo);
+                    result.RedirectingTo = fullUri.ToString();
+                }
+            }
+        }
     }
 }
diff --git a/src/Jackett/Utils/StringUtil.cs b/src/Jackett/Utils/StringUtil.cs
index 6a40ca5f96858d239f0539fa4a68114ad09164eb..6fa5864e416c51400aa53eb227e2ae1fa55bb632 100644
--- a/src/Jackett/Utils/StringUtil.cs
+++ b/src/Jackett/Utils/StringUtil.cs
@@ -1,9 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Linq;
+using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
+using System.Web;
 
 namespace Jackett.Utils
 {
@@ -21,5 +24,43 @@ namespace Jackett.Utils
             return Encoding.UTF8.GetString(Convert.FromBase64String(str));
         }
 
+        public static string Hash(string input)
+        {
+            // Use input string to calculate MD5 hash
+            MD5 md5 = System.Security.Cryptography.MD5.Create();
+            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
+            byte[] hashBytes = md5.ComputeHash(inputBytes);
+
+            // Convert the byte array to hexadecimal string
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < hashBytes.Length; i++)
+            {
+                sb.Append(hashBytes[i].ToString("X2"));
+            }
+            return sb.ToString();
+        }
+
+
+        public static string GetExceptionDetails(this Exception exception)
+        {
+            var properties = exception.GetType()
+                                    .GetProperties();
+            var fields = properties
+                             .Select(property => new {
+                                 Name = property.Name,
+                                 Value = property.GetValue(exception, null)
+                             })
+                             .Select(x => String.Format(
+                                 "{0} = {1}",
+                                 x.Name,
+                                 x.Value != null ? x.Value.ToString() : String.Empty
+                             ));
+            return String.Join("\n", fields);
+        }
+
+        public static string GetQueryString(this NameValueCollection collection)
+        {
+            return string.Join("&", collection.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(collection[a])));
+        }
     }
 }
diff --git a/src/Jackett/Utils/TorznabCapsUtil.cs b/src/Jackett/Utils/TorznabCapsUtil.cs
index 789d38236baa1abe81ebae9470ef671dcbc44095..35c3b953fd6ec0de34a3558cc8d835f297eda944 100644
--- a/src/Jackett/Utils/TorznabCapsUtil.cs
+++ b/src/Jackett/Utils/TorznabCapsUtil.cs
@@ -12,16 +12,12 @@ namespace Jackett.Utils
         public static TorznabCapabilities CreateDefaultTorznabTVCaps()
         {
             var caps = new TorznabCapabilities();
-            caps.SearchAvailable = true;
-            caps.TVSearchAvailable = true;
-            caps.SupportsTVRageSearch = false;
-            caps.Categories.AddRange(new[] { 
-                new TorznabCategory { ID = "5000", Name = "TV" },
-                new TorznabCategory { ID = "5030", Name = "TV/SD" },
-                new TorznabCategory { ID = "5040", Name = "TV/HD" } 
+            caps.Categories.AddRange(new[] {
+                TorznabCatType.TV,
+                TorznabCatType.TVSD,
+                TorznabCatType.TVHD
             });
             return caps;
         }
-
     }
 }
diff --git a/src/Jackett/WebAPIExceptionLogger.cs b/src/Jackett/WebAPIExceptionLogger.cs
new file mode 100644
index 0000000000000000000000000000000000000000..240ffda7c3512f80b0ef05a4fd0c36e6d089daa9
--- /dev/null
+++ b/src/Jackett/WebAPIExceptionLogger.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http.ExceptionHandling;
+using Jackett.Utils;
+
+namespace Jackett
+{
+    class WebAPIExceptionLogger : IExceptionLogger
+    {
+        public async Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken)
+        {
+            // OWIN seems to give lots of these exceptions but we are not interested in them.
+            if (context.Exception.Message != "Error while copying content to a stream.")
+            {
+                Engine.Logger.Error("Unhandled exception: " + context.Exception.GetExceptionDetails());
+                var request = await context.Request.Content.ReadAsStringAsync();
+                Engine.Logger.Error("Unhandled exception url: " + context.Request.RequestUri);
+            }
+        }
+    }
+}
diff --git a/src/Jackett/packages.config b/src/Jackett/packages.config
index 13069d9740fa82c09c07a04f7e14a45ff111d68f..741a3aaaa78511894fd2e7b99766de8ea3ac44e7 100644
--- a/src/Jackett/packages.config
+++ b/src/Jackett/packages.config
@@ -1,29 +1,29 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="Autofac" version="3.5.2" targetFramework="net451" />
-  <package id="Autofac.Owin" version="3.1.0" targetFramework="net451" />
-  <package id="Autofac.WebApi" version="3.1.0" targetFramework="net451" />
-  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net451" />
-  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net451" />
-  <package id="AutoMapper" version="3.3.1" targetFramework="net451" />
-  <package id="CsQuery" version="1.3.4" targetFramework="net451" />
-  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net451" />
-  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net451" />
-  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net451" />
-  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net451" />
-  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net451" />
-  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net451" />
-  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net451" />
-  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net451" />
-  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net451" />
-  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net451" />
-  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net451" />
-  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net451" />
-  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net451" />
-  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net451" />
-  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net451" />
-  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net451" />
-  <package id="NLog" version="4.0.1" targetFramework="net451" />
-  <package id="NLog.Windows.Forms" version="2.0.0.0" targetFramework="net451" />
-  <package id="Owin" version="1.0" targetFramework="net451" />
+  <package id="Autofac" version="3.5.2" targetFramework="net45" />
+  <package id="Autofac.Owin" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi" version="3.1.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2" version="3.4.0" targetFramework="net45" />
+  <package id="Autofac.WebApi2.Owin" version="3.2.0" targetFramework="net45" />
+  <package id="AutoMapper" version="3.3.1" targetFramework="net45" />
+  <package id="CsQuery" version="1.3.4" targetFramework="net45" />
+  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
+  <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
+  <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+  <package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.FileSystems" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" />
+  <package id="Microsoft.Owin.StaticFiles" version="3.0.1" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+  <package id="NLog" version="4.0.1" targetFramework="net45" />
+  <package id="NLog.Windows.Forms" version="2.0.0.0" targetFramework="net45" />
+  <package id="Owin" version="1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file