diff --git a/src/Jackett.Console/ConsoleOptions.cs b/src/Jackett.Console/ConsoleOptions.cs
index 635eb2e2ac4dc6b6d89e8c6b1e6eb5684d1e46bf..74982506bbc13e008eead3763dedb66506b62c52 100644
--- a/src/Jackett.Console/ConsoleOptions.cs
+++ b/src/Jackett.Console/ConsoleOptions.cs
@@ -27,6 +27,9 @@ namespace Jackett.Console
         [Option('c', "UseClient",  HelpText = "Override web client selection. [automatic(Default)/libcurl/safecurl/httpclient]")]
         public string Client { get; set; }
 
+        [Option('j', "ProxyConnection", HelpText = "use proxy - e.g. 127.0.0.1:8888")]
+        public string ProxyConnection { get; set; }
+
         [Option('s', "Start",  HelpText = "Start the Jacket Windows service (Must be admin)")]
         public bool StartService { get; set; }
 
diff --git a/src/Jackett.Console/Program.cs b/src/Jackett.Console/Program.cs
index a5b5ad94119f1ff243547f34da39aa87d8c5df04..f5a3e8d70ab734415296ce2324d8aa7839cc0f35 100644
--- a/src/Jackett.Console/Program.cs
+++ b/src/Jackett.Console/Program.cs
@@ -1,220 +1,226 @@
-using CommandLine;
-using CommandLine.Text;
-using Jackett;
-using Jackett.Console;
-using Jackett.Indexers;
-using Jackett.Utils;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace JackettConsole
-{
-    public class Program
-    {
-        static void Main(string[] args)
-        {
-            try
-            {
-                var options = new ConsoleOptions();
-                if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
-                {
-                    if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
-                    {
-                        var help = new HelpText();
-                        var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
-                        Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
-                        Console.WriteLine("Switch error: " + errors);
-                        Console.WriteLine("See --help for further details on switches.");
-                        Environment.ExitCode = 1;
-                        return;
-                    }
-                    else
-                    {
-
-                        var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
-                        text.Copyright = " ";
-                        text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
-                        Console.WriteLine(text);
-                        Environment.ExitCode = 1;
-                        return;
-                    }
-                }
-                else
-                {
-
-                    if (options.ListenPublic && options.ListenPrivate)
-                    {
-                        Console.WriteLine("You can only use listen private OR listen publicly.");
-                        Environment.ExitCode = 1;
-                        return;
-                    }
-                    /*  ======     Options    =====  */
-
-                    // SSL Fix
-                    Startup.DoSSLFix = options.SSLFix;
-
-                    // Use curl
-                    if (options.Client != null)
-                        Startup.ClientOverride = options.Client.ToLowerInvariant();
-
-                    // Logging
-                    if (options.Logging)
-                        Startup.LogRequests = true;
-
-                    // Tracing
-                    if (options.Tracing)
-                        Startup.TracingEnabled = true;
-
-                    // Log after the fact as using the logger will cause the options above to be used
-
-                    if (options.Logging)
-                        Engine.Logger.Info("Logging enabled.");
-
-                    if (options.Tracing)
-                        Engine.Logger.Info("Tracing enabled.");
-
-                    if (options.SSLFix == true)
-                        Engine.Logger.Info("SSL ECC workaround enabled.");
-                    else if (options.SSLFix == false)
-                        Engine.Logger.Info("SSL ECC workaround has been disabled.");
-
-                    // Ignore SSL errors on Curl
-                    Startup.IgnoreSslErrors = options.IgnoreSslErrors;
-                    if (options.IgnoreSslErrors == true)
-                    {
-                        Engine.Logger.Info("Curl will ignore SSL certificate errors.");
-                    }
-
-                    /*  ======     Actions    =====  */
-
-                    // Install service
-                    if (options.Install)
-                    {
-                        Engine.ServiceConfig.Install();
-                        return;
-                    }
-
-                    // Uninstall service
-                    if (options.Uninstall)
-                    {
-                        Engine.Server.ReserveUrls(doInstall: false);
-                        Engine.ServiceConfig.Uninstall();
-                        return;
-                    }
-
-                    // Reserve urls
-                    if (options.ReserveUrls)
-                    {
-                        Engine.Server.ReserveUrls(doInstall: true);
-                        return;
-                    }
-
-                    // Start Service
-                    if (options.StartService)
-                    {
-                        if (!Engine.ServiceConfig.ServiceRunning())
-                        {
-                            Engine.ServiceConfig.Start();
-                        }
-                        return;
-                    }
-
-                    // Stop Service
-                    if (options.StopService)
-                    {
-                        if (Engine.ServiceConfig.ServiceRunning())
-                        {
-                            Engine.ServiceConfig.Stop();
-                        }
-                        return;
-                    }
-
-                    // Migrate settings
-                    if (options.MigrateSettings)
-                    {
-                        Engine.ConfigService.PerformMigration();
-                        return;
-                    }
-
-
-                    // Show Version
-                    if (options.ShowVersion)
-                    {
-                        Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
-                        return;
-                    }
-
-                    /*  ======     Overrides    =====  */
-
-                    // Override listen public
-                    if (options.ListenPublic || options.ListenPrivate)
-                    {
-                        if (Engine.Server.Config.AllowExternal != options.ListenPublic)
-                        {
-                            Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
-                            Engine.Server.Config.AllowExternal = options.ListenPublic;
-                            if (System.Environment.OSVersion.Platform != PlatformID.Unix)
-                            {
-                                if (ServerUtil.IsUserAdministrator())
-                                {
-                                    Engine.Server.ReserveUrls(doInstall: true);
-                                }
-                                else
-                                {
-                                    Engine.Logger.Error("Unable to switch to public listening without admin rights.");
-                                    Environment.ExitCode = 1;
-                                    return;
-                                }
-                            }
-
-                            Engine.Server.SaveConfig();
-                        }
-                    }
-
-                    // Override port
-                    if (options.Port != 0)
-                    {
-                        if (Engine.Server.Config.Port != options.Port)
-                        {
-                            Engine.Logger.Info("Overriding port to " + options.Port);
-                            Engine.Server.Config.Port = options.Port;
-                            if (System.Environment.OSVersion.Platform != PlatformID.Unix)
-                            {
-                                if (ServerUtil.IsUserAdministrator())
-                                {
-                                    Engine.Server.ReserveUrls(doInstall: true);
-                                }
-                                else
-                                {
-                                    Engine.Logger.Error("Unable to switch ports when not running as administrator");
-                                    Environment.ExitCode = 1;
-                                    return;
-                                }
-                            }
-
-                            Engine.Server.SaveConfig();
-                        }
-                    }
-                }
-
-                Engine.Server.Initalize();
-                Engine.Server.Start();
-                Engine.RunTime.Spin();
-                Engine.Logger.Info("Server thread exit");
-            }
-            catch (Exception e)
-            {
-                Engine.Logger.Error(e, "Top level exception");
-            }
-        }
-    }
-}
-
+using CommandLine;
+using CommandLine.Text;
+using Jackett;
+using Jackett.Console;
+using Jackett.Indexers;
+using Jackett.Utils;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace JackettConsole
+{
+    public class Program
+    {
+        static void Main(string[] args)
+        {
+            try
+            {
+                var options = new ConsoleOptions();
+                if (!Parser.Default.ParseArguments(args, options) || options.ShowHelp == true)
+                {
+                    if (options.LastParserState != null && options.LastParserState.Errors.Count > 0)
+                    {
+                        var help = new HelpText();
+                        var errors = help.RenderParsingErrorsText(options, 2); // indent with two spaces
+                        Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
+                        Console.WriteLine("Switch error: " + errors);
+                        Console.WriteLine("See --help for further details on switches.");
+                        Environment.ExitCode = 1;
+                        return;
+                    }
+                    else
+                    {
+
+                        var text = HelpText.AutoBuild(options, (HelpText current) => HelpText.DefaultParsingErrorsHandler(options, current));
+                        text.Copyright = " ";
+                        text.Heading = "Jackett v" + Engine.ConfigService.GetVersion() + " options:";
+                        Console.WriteLine(text);
+                        Environment.ExitCode = 1;
+                        return;
+                    }
+                }
+                else
+                {
+
+                    if (options.ListenPublic && options.ListenPrivate)
+                    {
+                        Console.WriteLine("You can only use listen private OR listen publicly.");
+                        Environment.ExitCode = 1;
+                        return;
+                    }
+                    /*  ======     Options    =====  */
+
+                    // SSL Fix
+                    Startup.DoSSLFix = options.SSLFix;
+
+                    // Use curl
+                    if (options.Client != null)
+                        Startup.ClientOverride = options.Client.ToLowerInvariant();
+
+                    // Use Proxy
+                    if (options.ProxyConnection != null)
+                    {
+                        Startup.ProxyConnection = options.ProxyConnection.ToLowerInvariant();
+                        Engine.Logger.Info("Proxy enabled. " + Startup.ProxyConnection);
+                    }
+                    // Logging
+                    if (options.Logging)
+                        Startup.LogRequests = true;
+
+                    // Tracing
+                    if (options.Tracing)
+                        Startup.TracingEnabled = true;
+
+                    // Log after the fact as using the logger will cause the options above to be used
+
+                    if (options.Logging)
+                        Engine.Logger.Info("Logging enabled.");
+
+                    if (options.Tracing)
+                        Engine.Logger.Info("Tracing enabled.");
+
+                    if (options.SSLFix == true)
+                        Engine.Logger.Info("SSL ECC workaround enabled.");
+                    else if (options.SSLFix == false)
+                        Engine.Logger.Info("SSL ECC workaround has been disabled.");
+
+                    // Ignore SSL errors on Curl
+                    Startup.IgnoreSslErrors = options.IgnoreSslErrors;
+                    if (options.IgnoreSslErrors == true)
+                    {
+                        Engine.Logger.Info("Curl will ignore SSL certificate errors.");
+                    }
+
+                    /*  ======     Actions    =====  */
+
+                    // Install service
+                    if (options.Install)
+                    {
+                        Engine.ServiceConfig.Install();
+                        return;
+                    }
+
+                    // Uninstall service
+                    if (options.Uninstall)
+                    {
+                        Engine.Server.ReserveUrls(doInstall: false);
+                        Engine.ServiceConfig.Uninstall();
+                        return;
+                    }
+
+                    // Reserve urls
+                    if (options.ReserveUrls)
+                    {
+                        Engine.Server.ReserveUrls(doInstall: true);
+                        return;
+                    }
+
+                    // Start Service
+                    if (options.StartService)
+                    {
+                        if (!Engine.ServiceConfig.ServiceRunning())
+                        {
+                            Engine.ServiceConfig.Start();
+                        }
+                        return;
+                    }
+
+                    // Stop Service
+                    if (options.StopService)
+                    {
+                        if (Engine.ServiceConfig.ServiceRunning())
+                        {
+                            Engine.ServiceConfig.Stop();
+                        }
+                        return;
+                    }
+
+                    // Migrate settings
+                    if (options.MigrateSettings)
+                    {
+                        Engine.ConfigService.PerformMigration();
+                        return;
+                    }
+
+
+                    // Show Version
+                    if (options.ShowVersion)
+                    {
+                        Console.WriteLine("Jackett v" + Engine.ConfigService.GetVersion());
+                        return;
+                    }
+
+                    /*  ======     Overrides    =====  */
+
+                    // Override listen public
+                    if (options.ListenPublic || options.ListenPrivate)
+                    {
+                        if (Engine.Server.Config.AllowExternal != options.ListenPublic)
+                        {
+                            Engine.Logger.Info("Overriding external access to " + options.ListenPublic);
+                            Engine.Server.Config.AllowExternal = options.ListenPublic;
+                            if (System.Environment.OSVersion.Platform != PlatformID.Unix)
+                            {
+                                if (ServerUtil.IsUserAdministrator())
+                                {
+                                    Engine.Server.ReserveUrls(doInstall: true);
+                                }
+                                else
+                                {
+                                    Engine.Logger.Error("Unable to switch to public listening without admin rights.");
+                                    Environment.ExitCode = 1;
+                                    return;
+                                }
+                            }
+
+                            Engine.Server.SaveConfig();
+                        }
+                    }
+
+                    // Override port
+                    if (options.Port != 0)
+                    {
+                        if (Engine.Server.Config.Port != options.Port)
+                        {
+                            Engine.Logger.Info("Overriding port to " + options.Port);
+                            Engine.Server.Config.Port = options.Port;
+                            if (System.Environment.OSVersion.Platform != PlatformID.Unix)
+                            {
+                                if (ServerUtil.IsUserAdministrator())
+                                {
+                                    Engine.Server.ReserveUrls(doInstall: true);
+                                }
+                                else
+                                {
+                                    Engine.Logger.Error("Unable to switch ports when not running as administrator");
+                                    Environment.ExitCode = 1;
+                                    return;
+                                }
+                            }
+
+                            Engine.Server.SaveConfig();
+                        }
+                    }
+                }
+
+                Engine.Server.Initalize();
+                Engine.Server.Start();
+                Engine.RunTime.Spin();
+                Engine.Logger.Info("Server thread exit");
+            }
+            catch (Exception e)
+            {
+                Engine.Logger.Error(e, "Top level exception");
+            }
+        }
+    }
+}
+
diff --git a/src/Jackett/Content/logos/bitsoup.png b/src/Jackett/Content/logos/bitsoup.png
new file mode 100644
index 0000000000000000000000000000000000000000..65c0459e16a2347b5af52d88523a7536e7e1b2d1
Binary files /dev/null and b/src/Jackett/Content/logos/bitsoup.png differ
diff --git a/src/Jackett/Content/logos/xspeeds.png b/src/Jackett/Content/logos/xspeeds.png
new file mode 100644
index 0000000000000000000000000000000000000000..81098b3461bb99d73a240dbf9f47bbc482028306
Binary files /dev/null and b/src/Jackett/Content/logos/xspeeds.png differ
diff --git a/src/Jackett/CurlHelper.cs b/src/Jackett/CurlHelper.cs
index c2a94cdd9e9c886fe573c19c5277f4351482f4c0..fe58143e4b15dde2cec63ec46b52230cc1460ee9 100644
--- a/src/Jackett/CurlHelper.cs
+++ b/src/Jackett/CurlHelper.cs
@@ -85,6 +85,7 @@ namespace Jackett
 
                 using (var easy = new CurlEasy())
                 {
+                    
                     easy.Url = curlRequest.Url;
                     easy.BufferSize = 64 * 1024;
                     easy.UserAgent = BrowserUtil.ChromeUserAgent;
@@ -139,7 +140,12 @@ namespace Jackett
                     {
                         easy.SetOpt(CurlOption.SslVerifyhost, false);
                         easy.SetOpt(CurlOption.SslVerifyPeer, false);
-                    }
+                    }
+
+                    if (Startup.ProxyConnection != null)
+                    {
+                        easy.SetOpt(CurlOption.Proxy, Startup.ProxyConnection);
+                    }                    
 
                     easy.Perform();
 
@@ -155,6 +161,15 @@ namespace Jackett
 
                 var headerBytes = Combine(headerBuffers.ToArray());
                 var headerString = Encoding.UTF8.GetString(headerBytes);
+                if (Startup.ProxyConnection != null)
+                {
+                    var firstcrlf = headerString.IndexOf("\r\n\r\n");
+                    var secondcrlf = headerString.IndexOf("\r\n\r\n", firstcrlf + 1);
+                    if (secondcrlf > 0)
+                    {
+                        headerString = headerString.Substring(firstcrlf + 4, secondcrlf - (firstcrlf));
+                    }
+                }
                 var headerParts = headerString.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
                 var headers = new List<string[]>();
                 var headerCount = 0;
diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs
index 404f550447e90acc58062f0036814eabb62d0e0b..377c2b12d8003246d426d642886a2275a3871a3d 100644
--- a/src/Jackett/Indexers/BaseIndexer.cs
+++ b/src/Jackett/Indexers/BaseIndexer.cs
@@ -147,37 +147,69 @@ namespace Jackett.Indexers
             }
         }
 
-        protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        protected async Task FollowIfRedirect(WebClientStringResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
         {
             var byteResult = new WebClientByteResult();
             // Map to byte
             Mapper.Map(response, byteResult);
-            await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies);
+            await FollowIfRedirect(byteResult, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
             // Map to string
             Mapper.Map(byteResult, response);
         }
 
-        protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        protected async Task FollowIfRedirect(WebClientByteResult response, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
         {
             // Follow up  to 5 redirects
             for (int i = 0; i < 5; i++)
             {
                 if (!response.IsRedirect)
                     break;
-                await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies);
+                await DoFollowIfRedirect(response, referrer, overrideRedirectUrl, overrideCookies, accumulateCookies);
+                if (accumulateCookies)
+                {
+                    CookieHeader = ResolveCookies((CookieHeader != null && CookieHeader != ""? CookieHeader + " " : "") + (overrideCookies != null && overrideCookies != "" ? overrideCookies + " " : "") + response.Cookies);
+                    overrideCookies = response.Cookies = CookieHeader;
+                }
+                if (overrideCookies != null && response.Cookies == null)
+                {
+                    response.Cookies = overrideCookies;
+                }
             }
         }
 
-        private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null)
+        private String ResolveCookies(String incomingCookies = "")
+        {
+            var redirRequestCookies = (CookieHeader != "" ? CookieHeader + " " : "") + incomingCookies;
+            System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\s]+)=([^=]+)(?:\s|$)");
+            Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
+            var matches = expression.Match(redirRequestCookies);
+            while (matches.Success)
+            {
+                if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
+                matches = matches.NextMatch();
+            }
+            return string.Join(" ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
+            
+        }
+
+        private async Task DoFollowIfRedirect(WebClientByteResult incomingResponse, string referrer = null, string overrideRedirectUrl = null, string overrideCookies = null, bool accumulateCookies = false)
         {
             if (incomingResponse.IsRedirect)
             {
+                var redirRequestCookies = "";
+                if (accumulateCookies)
+                {
+                    redirRequestCookies = ResolveCookies((CookieHeader != "" ? CookieHeader + " " : "") + (overrideCookies != null ? overrideCookies : ""));
+                } else
+                {
+                    redirRequestCookies = (overrideCookies != null ? overrideCookies : "");
+                }
                 // Do redirect
                 var redirectedResponse = await webclient.GetBytes(new WebRequest()
                 {
                     Url = overrideRedirectUrl ?? incomingResponse.RedirectingTo,
                     Referer = referrer,
-                    Cookies = overrideCookies ?? CookieHeader
+                    Cookies = redirRequestCookies
                 });
                 Mapper.Map(redirectedResponse, incomingResponse);
             }
@@ -233,7 +265,7 @@ namespace Jackett.Indexers
         public async virtual Task<byte[]> Download(Uri link)
         {
             var response = await RequestBytesWithCookiesAndRetry(link.ToString());
-            if(response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
+            if (response.Status != System.Net.HttpStatusCode.OK && response.Status != System.Net.HttpStatusCode.Continue && response.Status != System.Net.HttpStatusCode.PartialContent)
             {
                 throw new Exception($"Remote server returned {response.Status.ToString()}");
             }
@@ -269,7 +301,7 @@ namespace Jackett.Indexers
                 Type = RequestType.GET,
                 Cookies = CookieHeader,
                 Referer = referer,
-                 Headers = headers
+                Headers = headers
             };
 
             if (cookieOverride != null)
@@ -277,7 +309,7 @@ namespace Jackett.Indexers
             return await webclient.GetString(request);
         }
 
-        protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string,string> headers = null)
+        protected async Task<WebClientStringResult> RequestStringWithCookiesAndRetry(string url, string cookieOverride = null, string referer = null, Dictionary<string, string> headers = null)
         {
             Exception lastException = null;
             for (int i = 0; i < 3; i++)
@@ -349,7 +381,7 @@ namespace Jackett.Indexers
             throw lastException;
         }
 
-        protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null)
+        protected async Task<WebClientStringResult> RequestLoginAndFollowRedirect(string url, IEnumerable<KeyValuePair<string, string>> data, string cookies, bool returnCookiesFromFirstCall, string redirectUrlOverride = null, string referer = null, bool accumulateCookies = false)
         {
             var request = new Utils.Clients.WebRequest()
             {
@@ -360,18 +392,22 @@ namespace Jackett.Indexers
                 PostData = data
             };
             var response = await webclient.GetString(request);
+            if (accumulateCookies)
+            {
+                response.Cookies = ResolveCookies((request.Cookies == null ? "" : request.Cookies + " ") + response.Cookies);
+            }
             var firstCallCookies = response.Cookies;
 
             if (response.IsRedirect)
             {
-                await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies);
+                await FollowIfRedirect(response, request.Url, redirectUrlOverride, response.Cookies, accumulateCookies);
             }
 
             if (returnCookiesFromFirstCall)
             {
-                response.Cookies = firstCallCookies;
+                response.Cookies = ResolveCookies(firstCallCookies + (accumulateCookies ? " " + response.Cookies : ""));
             }
-
+            
             return response;
         }
 
diff --git a/src/Jackett/Indexers/BitSoup.cs b/src/Jackett/Indexers/BitSoup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ee450198ba370a498f0728accfe259b7f7e278ec
--- /dev/null
+++ b/src/Jackett/Indexers/BitSoup.cs
@@ -0,0 +1,263 @@
+using CsQuery;
+using Jackett.Models;
+using Jackett.Services;
+using Jackett.Utils;
+using Jackett.Utils.Clients;
+using Newtonsoft.Json.Linq;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using Jackett.Models.IndexerConfig;
+using System.Collections.Specialized;
+using System.Text.RegularExpressions;
+
+namespace Jackett.Indexers
+{
+    public class BitSoup : BaseIndexer, IIndexer
+    {
+        private string UseLink { get { return (this.configData.AlternateLink.Value != null && this.configData.AlternateLink.Value != "" ? this.configData.AlternateLink.Value : SiteLink); } }
+        private string BrowseUrl { get { return UseLink + "browse.php"; } }
+        private string LoginUrl { get { return UseLink + "takelogin.php"; } }
+        private string LoginReferer {  get { return UseLink + "login.php";  } }
+        private List<String> KnownURLs = new List<String>{ "https://www.bitsoup.me/","https://www.bitsoup.org/"};
+
+        new NxtGnConfigurationData configData
+        {
+            get { return (NxtGnConfigurationData)base.configData; }
+            set { base.configData = value; }
+        }
+
+        public BitSoup(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
+            : base(name: "BitSoup",
+                description: "SoupieBits",
+                link: "https://www.bitsoup.me/",
+                caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
+                manager: i,
+                client: wc,
+                logger: l,
+                p: ps,
+                configData: new NxtGnConfigurationData())
+        {
+            this.configData.DisplayText.Value = this.DisplayName + " has multiple URLs.  The default (" + this.SiteLink + ") can be changed by entering a new value in the box below.";
+            this.configData.DisplayText.Value += "The following are some known URLs for " + this.DisplayName;
+            this.configData.DisplayText.Value += "<ul><li>" + String.Join("</li><li>", this.KnownURLs.ToArray()) + "</li></ul>";
+
+            //AddCategoryMapping("624", TorznabCatType.Console);
+            //AddCategoryMapping("307", TorznabCatType.ConsoleNDS);
+            //AddCategoryMapping("308", TorznabCatType.ConsolePSP);
+            AddCategoryMapping("35", TorznabCatType.ConsoleWii);
+            //AddCategoryMapping("309", TorznabCatType.ConsoleXbox);
+            AddCategoryMapping("12", TorznabCatType.ConsoleXbox360);
+            //AddCategoryMapping("305", TorznabCatType.ConsoleWiiwareVC);
+            //AddCategoryMapping("309", TorznabCatType.ConsoleXBOX360DLC);
+            AddCategoryMapping("38", TorznabCatType.ConsolePS3);
+            //AddCategoryMapping("239", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("245", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("246", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("626", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("628", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("630", TorznabCatType.ConsoleOther);
+            //AddCategoryMapping("307", TorznabCatType.Console3DS);
+            //AddCategoryMapping("308", TorznabCatType.ConsolePSVita);
+            //AddCategoryMapping("307", TorznabCatType.ConsoleWiiU);
+            //AddCategoryMapping("309", TorznabCatType.ConsoleXboxOne);
+            //AddCategoryMapping("308", TorznabCatType.ConsolePS4);
+            //AddCategoryMapping("631", TorznabCatType.Movies);
+            //AddCategoryMapping("631", TorznabCatType.MoviesForeign);
+            //AddCategoryMapping("455", TorznabCatType.MoviesOther);
+            //AddCategoryMapping("633", TorznabCatType.MoviesOther);
+            AddCategoryMapping("19", TorznabCatType.MoviesSD);
+            AddCategoryMapping("41", TorznabCatType.MoviesHD);
+            AddCategoryMapping("17", TorznabCatType.Movies3D);
+            AddCategoryMapping("80", TorznabCatType.MoviesBluRay);
+            AddCategoryMapping("20", TorznabCatType.MoviesDVD);
+            //AddCategoryMapping("631", TorznabCatType.MoviesWEBDL);
+            AddCategoryMapping("6", TorznabCatType.Audio);
+            //AddCategoryMapping("623", TorznabCatType.AudioMP3);
+            AddCategoryMapping("29", TorznabCatType.AudioVideo);
+            //AddCategoryMapping("402", TorznabCatType.AudioVideo);
+            AddCategoryMapping("5", TorznabCatType.AudioAudiobook);
+            //AddCategoryMapping("1", TorznabCatType.AudioLossless);
+            //AddCategoryMapping("403", TorznabCatType.AudioOther);
+            //AddCategoryMapping("642", TorznabCatType.AudioOther);
+            //AddCategoryMapping("1", TorznabCatType.AudioForeign);
+            //AddCategoryMapping("233", TorznabCatType.PC);
+            //AddCategoryMapping("236", TorznabCatType.PC);
+            //AddCategoryMapping("1", TorznabCatType.PC0day);
+            AddCategoryMapping("1", TorznabCatType.PCISO);
+            //AddCategoryMapping("235", TorznabCatType.PCMac);
+            //AddCategoryMapping("627", TorznabCatType.PCPhoneOther);
+            AddCategoryMapping("21", TorznabCatType.PCGames);
+            AddCategoryMapping("4", TorznabCatType.PCGames);
+            //AddCategoryMapping("625", TorznabCatType.PCPhoneIOS);
+            //AddCategoryMapping("625", TorznabCatType.PCPhoneAndroid);
+            AddCategoryMapping("45", TorznabCatType.TV);
+            //AddCategoryMapping("433", TorznabCatType.TV);
+            //AddCategoryMapping("639", TorznabCatType.TVWEBDL);
+            //AddCategoryMapping("433", TorznabCatType.TVWEBDL);
+            //AddCategoryMapping("639", TorznabCatType.TVFOREIGN);
+            //AddCategoryMapping("433", TorznabCatType.TVFOREIGN);
+            AddCategoryMapping("7", TorznabCatType.TVSD);
+            AddCategoryMapping("49", TorznabCatType.TVSD);
+            AddCategoryMapping("42", TorznabCatType.TVHD);
+            //AddCategoryMapping("433", TorznabCatType.TVHD);
+            //AddCategoryMapping("635", TorznabCatType.TVOTHER);
+            //AddCategoryMapping("636", TorznabCatType.TVSport);
+            AddCategoryMapping("23", TorznabCatType.TVAnime);
+            //AddCategoryMapping("634", TorznabCatType.TVDocumentary);
+            AddCategoryMapping("9", TorznabCatType.XXX);
+            //AddCategoryMapping("1", TorznabCatType.XXXDVD);
+            //AddCategoryMapping("1", TorznabCatType.XXXWMV);
+            //AddCategoryMapping("1", TorznabCatType.XXXXviD);
+            //AddCategoryMapping("1", TorznabCatType.XXXx264);
+            //AddCategoryMapping("1", TorznabCatType.XXXOther);
+            //AddCategoryMapping("1", TorznabCatType.XXXImageset);
+            //AddCategoryMapping("1", TorznabCatType.XXXPacks);
+            //AddCategoryMapping("340", TorznabCatType.Other);
+            //AddCategoryMapping("342", TorznabCatType.Other);
+            //AddCategoryMapping("344", TorznabCatType.Other);
+            //AddCategoryMapping("391", TorznabCatType.Other);
+            //AddCategoryMapping("392", TorznabCatType.Other);
+            //AddCategoryMapping("393", TorznabCatType.Other);
+            //AddCategoryMapping("394", TorznabCatType.Other);
+            //AddCategoryMapping("234", TorznabCatType.Other);
+            //AddCategoryMapping("638", TorznabCatType.Other);
+            //AddCategoryMapping("629", TorznabCatType.Other);
+            //AddCategoryMapping("1", TorznabCatType.OtherMisc);
+            //AddCategoryMapping("1", TorznabCatType.OtherHashed);
+            //AddCategoryMapping("408", TorznabCatType.Books);
+            AddCategoryMapping("24", TorznabCatType.BooksEbook);
+            //AddCategoryMapping("406", TorznabCatType.BooksComics);
+            //AddCategoryMapping("407", TorznabCatType.BooksComics);
+            //AddCategoryMapping("409", TorznabCatType.BooksComics);
+            //AddCategoryMapping("410", TorznabCatType.BooksMagazines);
+            //AddCategoryMapping("1", TorznabCatType.BooksTechnical);
+            //AddCategoryMapping("1", TorznabCatType.BooksOther);
+            //AddCategoryMapping("1", TorznabCatType.BooksForeign);
+        }
+
+        public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
+        {
+            configData.LoadValuesFromJson(configJson);
+            if (configData.AlternateLink.Value != null && configData.AlternateLink.Value != "")
+            {
+                if (!configData.AlternateLink.Value.EndsWith("/"))
+                {
+                    configData.AlternateLink.Value = null;
+                    throw new Exception("AlternateLink must end with a slash.");
+                }
+                var match = Regex.Match(configData.AlternateLink.Value, "^https?:\\/\\/(?:[\\w]+\\.)+(?:[a-zA-Z]+)\\/$");
+                if (!match.Success)
+                {
+                    configData.AlternateLink.Value = null;
+                    throw new Exception("AlternateLink must be a valid url.");
+                }              
+            }
+            var pairs = new Dictionary<string, string> {
+                { "username", configData.Username.Value },
+                { "password", configData.Password.Value },
+                
+            };
+
+            var loginPage = await RequestStringWithCookies(UseLink, string.Empty);
+
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, loginPage.Cookies, true, null, LoginReferer, true);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
+            {
+                CQ dom = result.Content;
+                var messageEl = dom["body > table.statusbar1 > tbody > tr > td > table > tbody > tr > td > table > tbody > tr > td"].First();
+                var errorMessage = messageEl.Text().Trim();
+                throw new ExceptionWithConfigData(errorMessage, configData);
+            });
+            return IndexerConfigurationStatus.RequiresTesting;
+        }
+
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
+        {
+            var releases = new List<ReleaseInfo>();
+            var searchString = query.GetQueryString();
+            var searchUrl = BrowseUrl;
+            var trackerCats = MapTorznabCapsToTrackers(query);
+            var queryCollection = new NameValueCollection();
+
+
+            queryCollection.Add("search", string.IsNullOrWhiteSpace(searchString)? "" : searchString);
+            if (trackerCats.Count > 1)
+            {
+               for (var ct = 0; ct < trackerCats.Count; ct++) queryCollection.Add("cat" + (ct+1), trackerCats.ElementAt(ct));
+            } else
+            {
+                queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
+            }
+            //queryCollection.Add("cat", (trackerCats.Count == 1 ? trackerCats.ElementAt(0) : "0"));
+            searchUrl += "?" + queryCollection.GetQueryString();
+            await ProcessPage(releases, searchUrl);
+
+            return releases;
+        }
+
+        private async Task ProcessPage(List<ReleaseInfo> releases, string searchUrl)
+        {
+            var response = await RequestStringWithCookiesAndRetry(searchUrl, null, BrowseUrl);
+            var results = response.Content;
+            try
+            {
+                CQ dom = results;
+
+                var rows = dom["table.koptekst tr"];
+                foreach (var row in rows.Skip(1))
+                {
+                    var release = new ReleaseInfo();
+
+                    release.Title = row.Cq().Find("td:eq(1) a").First().Text().Trim();
+                    release.Comments = new Uri(UseLink + row.Cq().Find("td:eq(1) a").First().Attr("href"));
+
+                    release.Link = new Uri(UseLink + row.Cq().Find("td:eq(2) a").First().Attr("href"));
+                    release.Description = release.Title;
+                    var cat = row.Cq().Find("td:eq(0) a").First().Attr("href").Substring(15);
+                    release.Category = MapTrackerCatToNewznab(cat);
+
+                    var added = row.Cq().Find("td:eq(7)").First().Text().Trim();
+                    release.PublishDate = DateTime.ParseExact(added, "yyyy-MM-ddH:mm:ss", CultureInfo.InvariantCulture);
+
+                    var sizeStr = row.Cq().Find("td:eq(8)").First().Text().Trim();
+                    release.Size = ReleaseInfo.GetBytes(sizeStr);
+
+                    release.Seeders = ParseUtil.CoerceInt(row.Cq().Find("td:eq(10)").First().Text().Trim());
+                    release.Peers = ParseUtil.CoerceInt(row.Cq().Find("td:eq(11)").First().Text().Trim()) + release.Seeders;
+
+                    releases.Add(release);
+                }
+            }
+            catch (Exception ex)
+            {
+                OnParseError(results, ex);
+            }
+        }
+
+        public class NxtGnConfigurationData : ConfigurationData
+        {
+            public StringItem Username { get; private set; }
+            public StringItem Password { get; private set; }
+            public DisplayItem DisplayText { get; private set; }
+            public StringItem AlternateLink { get; set; }
+           
+
+            public NxtGnConfigurationData()
+            {
+                Username = new StringItem { Name = "Username" };
+                Password = new StringItem { Name = "Password" };
+                DisplayText = new DisplayItem("") { Name = "" };
+                AlternateLink = new StringItem { Name = "AlternateLinks" };
+            }
+            
+        }
+    }
+}
diff --git a/src/Jackett/Indexers/XSpeeds.cs b/src/Jackett/Indexers/XSpeeds.cs
new file mode 100644
index 0000000000000000000000000000000000000000..be7027358fb63fde9b8edeb82a49babafbe94159
--- /dev/null
+++ b/src/Jackett/Indexers/XSpeeds.cs
@@ -0,0 +1,273 @@
+using CsQuery;
+using Jackett.Models;
+using Jackett.Services;
+using Jackett.Utils;
+using Jackett.Utils.Clients;
+using Newtonsoft.Json.Linq;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using Jackett.Models.IndexerConfig;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+
+namespace Jackett.Indexers
+{
+    public class XSpeeds : BaseIndexer, IIndexer
+    {
+        string LoginUrl { get { return SiteLink + "takelogin.php"; } }
+        string GetRSSKeyUrl { get { return SiteLink + "getrss.php"; } }
+        string SearchUrl { get { return SiteLink + "browse.php"; } }
+        string RSSUrl { get { return SiteLink + "rss.php?secret_key={0}&feedtype=download&timezone=0&showrows=50&categories=all"; } }
+        string CommentUrl { get { return SiteLink + "details.php?id={0}"; } }
+        string DownloadUrl { get { return SiteLink + "download.php?id={0}"; } }
+
+        new ConfigurationDataBasicLoginWithRSSAndDisplay configData
+        {
+            get {return (ConfigurationDataBasicLoginWithRSSAndDisplay)base.configData; }
+            set { base.configData = value; }
+        }
+
+        public XSpeeds(IIndexerManagerService i, IWebClient wc, Logger l, IProtectionService ps)
+            : base(name: "XSpeeds",
+                description: "XSpeeds",
+                link: "https://www.xspeeds.eu/",
+                caps: TorznabUtil.CreateDefaultTorznabTVCaps(),
+                manager: i,
+                client: wc,
+                logger: l,
+                p: ps,
+                configData: new ConfigurationDataBasicLoginWithRSSAndDisplay())
+        {
+            this.configData.DisplayText.Value = "Expect an initial delay (often around 10 seconds) due to XSpeeds CloudFlare DDoS protection";
+            this.configData.DisplayText.Name = "Notice";
+            AddCategoryMapping(70, TorznabCatType.TVAnime);
+            AddCategoryMapping(80, TorznabCatType.AudioAudiobook);
+            AddCategoryMapping(66, TorznabCatType.MoviesBluRay);
+            AddCategoryMapping(48, TorznabCatType.Books);
+            AddCategoryMapping(68, TorznabCatType.MoviesOther);
+            AddCategoryMapping(65, TorznabCatType.TVDocumentary);
+            AddCategoryMapping(10, TorznabCatType.MoviesDVD);
+            AddCategoryMapping(74, TorznabCatType.TVOTHER);
+            AddCategoryMapping(44, TorznabCatType.TVSport);
+            AddCategoryMapping(12, TorznabCatType.Movies);
+            AddCategoryMapping(13, TorznabCatType.Audio);
+            AddCategoryMapping(6, TorznabCatType.PC);
+            AddCategoryMapping(4, TorznabCatType.PC);
+            AddCategoryMapping(31, TorznabCatType.ConsolePS3);
+            AddCategoryMapping(31, TorznabCatType.ConsolePS4);
+            AddCategoryMapping(20, TorznabCatType.TVSport);
+            AddCategoryMapping(86, TorznabCatType.TVSport);
+            AddCategoryMapping(47, TorznabCatType.TVHD);
+            AddCategoryMapping(16, TorznabCatType.TVSD);
+            AddCategoryMapping(7, TorznabCatType.ConsoleWii);
+            AddCategoryMapping(8, TorznabCatType.ConsoleXbox);
+
+            // RSS Textual categories
+            AddCategoryMapping("Apps", TorznabCatType.PC);
+            AddCategoryMapping("Music", TorznabCatType.Audio);
+            AddCategoryMapping("Audiobooks", TorznabCatType.AudioAudiobook);
+            
+        }
+
+        public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
+        {
+            configData.LoadValuesFromJson(configJson);
+            var pairs = new Dictionary<string, string> {
+                { "username", configData.Username.Value },
+                { "password", configData.Password.Value }
+            };
+
+            var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, null, SiteLink, true);
+            result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink,true);
+            await ConfigureIfOK(result.Cookies, result.Content != null && result.Content.Contains("logout.php"), () =>
+            {
+                CQ dom = result.Content;
+                var errorMessage = dom[".left_side table:eq(0) tr:eq(1)"].Text().Trim().Replace("\n\t", " ");
+                throw new ExceptionWithConfigData(errorMessage, configData);
+            });
+
+            try
+            {
+                // Get RSS key
+                var rssParams = new Dictionary<string, string> {
+                { "feedtype", "download" },
+                { "timezone", "0" },
+                { "showrows", "50" }
+            };
+                var rssPage = await PostDataWithCookies(GetRSSKeyUrl, rssParams, result.Cookies);
+                var match = Regex.Match(rssPage.Content, "(?<=secret_key\\=)([a-zA-z0-9]*)");
+                configData.RSSKey.Value = match.Success ? match.Value : string.Empty;
+                if (string.IsNullOrWhiteSpace(configData.RSSKey.Value))
+                    throw new Exception("Failed to get RSS Key");
+                SaveConfig();
+            }
+            catch (Exception e)
+            {
+                IsConfigured = false;
+                throw e;
+            }
+            return IndexerConfigurationStatus.RequiresTesting;
+        }
+
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
+        {
+            var releases = new List<ReleaseInfo>();
+            var searchString = query.GetQueryString();
+            var prevCook = CookieHeader + "";
+
+            // If we have no query use the RSS Page as their server is slow enough at times!
+            if (string.IsNullOrWhiteSpace(searchString))
+            {
+                var rssPage = await RequestStringWithCookiesAndRetry(string.Format(RSSUrl, configData.RSSKey.Value));
+                if (rssPage.Content.EndsWith("\0")) {
+                    rssPage.Content = rssPage.Content.Substring(0, rssPage.Content.Length - 1);
+                }
+                var rssDoc = XDocument.Parse(rssPage.Content);
+
+                foreach (var item in rssDoc.Descendants("item"))
+                {
+                    var title = item.Descendants("title").First().Value;
+                    var description = item.Descendants("description").First().Value;
+                    var link = item.Descendants("link").First().Value;
+                    var category = item.Descendants("category").First().Value;
+                    var date = item.Descendants("pubDate").First().Value;
+
+                    var torrentIdMatch = Regex.Match(link, "(?<=id=)(\\d)*");
+                    var torrentId = torrentIdMatch.Success ? torrentIdMatch.Value : string.Empty;
+                    if (string.IsNullOrWhiteSpace(torrentId))
+                        throw new Exception("Missing torrent id");
+
+                    var infoMatch = Regex.Match(description, @"Category:\W(?<cat>.*)\W\/\WSeeders:\W(?<seeders>\d*)\W\/\WLeechers:\W(?<leechers>\d*)\W\/\WSize:\W(?<size>[\d\.]*\W\S*)");
+                    if (!infoMatch.Success)
+                        throw new Exception("Unable to find info");
+
+                    var release = new ReleaseInfo()
+                    {
+                        Title = title,
+                        Description = title,
+                        Guid = new Uri(string.Format(DownloadUrl, torrentId)),
+                        Comments = new Uri(string.Format(CommentUrl, torrentId)),
+                        PublishDate = DateTime.ParseExact(date, "yyyy-MM-dd H:mm:ss", CultureInfo.InvariantCulture), //2015-08-08 21:20:31 
+                        Link = new Uri(string.Format(DownloadUrl, torrentId)),
+                        Seeders = ParseUtil.CoerceInt(infoMatch.Groups["seeders"].Value),
+                        Peers = ParseUtil.CoerceInt(infoMatch.Groups["leechers"].Value),
+                        Size = ReleaseInfo.GetBytes(infoMatch.Groups["size"].Value),
+                        Category = MapTrackerCatToNewznab(infoMatch.Groups["cat"].Value)
+                    };
+
+                    // If its not apps or audio we can only mark as general TV
+                    if (release.Category == 0)
+                        release.Category = 5030;
+
+                    release.Peers += release.Seeders;
+                    releases.Add(release);
+                }
+            }
+            else
+            {
+                if (searchString.Length < 3)
+                {
+                    OnParseError("", new Exception("Minimum search length is 3"));
+                    return releases;
+                }
+                var searchParams = new Dictionary<string, string> {
+                    { "do", "search" },
+                    { "keywords",  searchString },
+                    { "search_type", "t_name" },
+                    { "category", "0" },
+                    { "include_dead_torrents", "no" }
+                };
+                var pairs = new Dictionary<string, string> {
+                    { "username", configData.Username.Value },
+                    { "password", configData.Password.Value }
+                };
+                var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, this.CookieHeader, true, null, SiteLink, true);
+                if (!result.Cookies.Trim().Equals(prevCook.Trim()))
+                {
+                    result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result.Cookies, true, SearchUrl, SiteLink, true);
+                }
+                this.CookieHeader = result.Cookies;
+
+                var attempt = 1;
+                var searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams,this.CookieHeader);
+                while (searchPage.IsRedirect && attempt < 3)
+                {
+                    // add any cookies
+                    var cookieString = this.CookieHeader;
+                    if (searchPage.Cookies != null)
+                    {
+                        cookieString += " " + searchPage.Cookies;
+                        // resolve cookie conflicts - really no need for this as the webclient will handle it
+                        System.Text.RegularExpressions.Regex expression = new System.Text.RegularExpressions.Regex(@"([^\s]+)=([^=]+)(?:\s|$)");
+                        Dictionary<string, string> cookieDIctionary = new Dictionary<string, string>();
+                        var matches = expression.Match(cookieString);
+                        while (matches.Success)
+                        {
+                            if (matches.Groups.Count > 2) cookieDIctionary[matches.Groups[1].Value] = matches.Groups[2].Value;
+                            matches = matches.NextMatch();
+                        }
+                        cookieString = string.Join(" ", cookieDIctionary.Select(kv => kv.Key.ToString() + "=" + kv.Value.ToString()).ToArray());
+                    }
+                    this.CookieHeader = cookieString;
+                    attempt++;
+                    searchPage = await PostDataWithCookiesAndRetry(SearchUrl, searchParams, this.CookieHeader);
+                }
+                try
+                {
+                    CQ dom = searchPage.Content;
+                    var rows = dom["#listtorrents tbody tr"];
+                    foreach (var row in rows.Skip(1))
+                    {
+                        var release = new ReleaseInfo();
+                        var qRow = row.Cq();
+
+                        release.Title = qRow.Find("td:eq(1) .tooltip-content div:eq(0)").Text();
+
+                        if (string.IsNullOrWhiteSpace(release.Title))
+                            continue;
+
+                        release.Description = release.Title;
+                        release.Guid = new Uri(qRow.Find("td:eq(2) a").Attr("href"));
+                        release.Link = release.Guid;
+                        release.Comments = new Uri(qRow.Find("td:eq(1) .tooltip-target a").Attr("href"));
+                        release.PublishDate = DateTime.ParseExact(qRow.Find("td:eq(1) div").Last().Text().Trim(), "dd-MM-yyyy H:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); //08-08-2015 12:51 
+                        release.Seeders = ParseUtil.CoerceInt(qRow.Find("td:eq(6)").Text());
+                        release.Peers = release.Seeders + ParseUtil.CoerceInt(qRow.Find("td:eq(7)").Text().Trim());
+                        release.Size = ReleaseInfo.GetBytes(qRow.Find("td:eq(4)").Text().Trim());
+
+
+                        var cat = row.Cq().Find("td:eq(0) a").First().Attr("href");
+                        var catSplit = cat.LastIndexOf('=');
+                        if (catSplit > -1)
+                            cat = cat.Substring(catSplit + 1);
+                        release.Category = MapTrackerCatToNewznab(cat);
+
+                        // If its not apps or audio we can only mark as general TV
+                        if (release.Category == 0)
+                            release.Category = 5030;
+
+                        releases.Add(release);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    OnParseError(searchPage.Content, ex);
+                }
+            }
+            if (!CookieHeader.Trim().Equals(prevCook.Trim()))
+            {
+                this.SaveConfig();
+            }
+            return releases;
+        }
+    }
+}
diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj
index 3792bdf6e711ce260d69680d76bdc1dbd7f0e156..628b554dd3767df9bee3d5d89769acf3519ed7ed 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -183,6 +183,7 @@
     <Compile Include="Controllers\DownloadController.cs" />
     <Compile Include="Engine.cs" />
     <Compile Include="Indexers\AlphaRatio.cs" />
+    <Compile Include="Indexers\BitSoup.cs" />
     <Compile Include="Indexers\BlueTigers.cs" />
     <Compile Include="Indexers\EuTorrents.cs" />
     <Compile Include="Indexers\Avistaz.cs" />
@@ -206,11 +207,13 @@
     <Compile Include="Indexers\ImmortalSeed.cs" />
     <Compile Include="Indexers\FileList.cs" />
     <Compile Include="Indexers\Abstract\AvistazTracker.cs" />
+    <Compile Include="Indexers\XSpeeds.cs" />
     <Compile Include="Models\GitHub\Asset.cs" />
     <Compile Include="Models\GitHub\Release.cs" />
     <Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" />
     <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilter.cs" />
     <Compile Include="Models\IndexerConfig\ConfigurationDataAPIKey.cs" />
+    <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSSAndDisplay.cs" />
     <Compile Include="Models\ManualSearchResult.cs" />
     <Compile Include="Indexers\TVChaosUK.cs" />
     <Compile Include="Indexers\NCore.cs" />
@@ -435,6 +438,7 @@
     <Content Include="Content\logos\beyondhd.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Content\logos\bitsoup.png" />
     <Content Include="Content\logos\bluetigers.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -558,6 +562,7 @@
     <Content Include="Content\logos\tvchaosuk.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Content\logos\xspeeds.png" />
     <Content Include="Content\setup_indexer.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -621,4 +626,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>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
index 499a6879128186ab072a5e4ed79a6fc096f4773a..134968542926d5c905ff86dfc6963e2a4cd6fe48 100644
--- a/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
+++ b/src/Jackett/Models/IndexerConfig/ConfigurationData.cs
@@ -132,7 +132,7 @@ namespace Jackett.Models.IndexerConfig
             if (!forDisplay)
             {
                 properties = properties
-                    .Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha)
+                    .Where(p => p.ItemType == ItemType.HiddenData || p.ItemType == ItemType.InputBool || p.ItemType == ItemType.InputString || p.ItemType == ItemType.Recaptcha || p.ItemType == ItemType.DisplayInfo)
                     .ToArray();
             }
 
diff --git a/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithRSSAndDisplay.cs b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithRSSAndDisplay.cs
new file mode 100644
index 0000000000000000000000000000000000000000..981d6117dbf9435f45d8cee9f0c6b83f7e42a7b0
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/ConfigurationDataBasicLoginWithRSSAndDisplay.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Jackett.Models.IndexerConfig
+{
+    public class ConfigurationDataBasicLoginWithRSSAndDisplay : ConfigurationData
+    {
+        public StringItem Username { get; private set; }
+        public StringItem Password { get; private set; }
+        public HiddenItem RSSKey { get; private set; }
+        public DisplayItem DisplayText { get; private set; }
+
+        public ConfigurationDataBasicLoginWithRSSAndDisplay()
+        {
+            Username = new StringItem { Name = "Username" };
+            Password = new StringItem { Name = "Password" };
+            RSSKey = new HiddenItem { Name = "RSSKey" };
+            DisplayText = new DisplayItem(""){ Name = "" };
+        }
+    }
+}
diff --git a/src/Jackett/Startup.cs b/src/Jackett/Startup.cs
index 2f6dd6d2b71ff231d398b16c1a18f26510c44f16..5aa8df8edba5b8e35a9443977fddc1f048e29784 100644
--- a/src/Jackett/Startup.cs
+++ b/src/Jackett/Startup.cs
@@ -1,4 +1,4 @@
-using Owin;
+using Owin;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -40,6 +40,12 @@ namespace Jackett
             set;
         }
 
+        public static string ProxyConnection
+        {
+            get;
+            set;
+        }
+
         public static bool? DoSSLFix
         {
             get;
diff --git a/src/Jackett/Utils/Clients/HttpWebClient.cs b/src/Jackett/Utils/Clients/HttpWebClient.cs
index d6d3406931548afd290fc412b6909953f12e95a3..2e2602d05b634e74bdacf2d1144e56fd648e8465 100644
--- a/src/Jackett/Utils/Clients/HttpWebClient.cs
+++ b/src/Jackett/Utils/Clients/HttpWebClient.cs
@@ -62,18 +62,28 @@ namespace Jackett.Utils.Clients
                     }
                 }
             }
-
+            var useProxy = false;
+            WebProxy proxyServer = null;
+            if (Startup.ProxyConnection != null)
+            {
+                proxyServer = new WebProxy(Startup.ProxyConnection, false);
+                useProxy = true;
+            }
             var client = new HttpClient(new HttpClientHandler
             {
                 CookieContainer = cookies,
                 AllowAutoRedirect = false, // Do not use this - Bugs ahoy! Lost cookies and more.
                 UseCookies = true,
+                Proxy = proxyServer,
+                UseProxy = useProxy
             });
+            
 
-            if(webRequest.EmulateBrowser)
-               client.DefaultRequestHeaders.Add("User-Agent",  BrowserUtil.ChromeUserAgent);
+            if (webRequest.EmulateBrowser)
+                client.DefaultRequestHeaders.Add("User-Agent",  BrowserUtil.ChromeUserAgent);
             else
-               client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
+                client.DefaultRequestHeaders.Add("User-Agent", "Jackett/" + configService.GetVersion());
+            
             HttpResponseMessage response = null;
             var request = new HttpRequestMessage();
             request.Headers.ExpectContinue = false;
@@ -118,6 +128,35 @@ namespace Jackett.Utils.Clients
 
             var result = new WebClientByteResult();
             result.Content = await response.Content.ReadAsByteArrayAsync();
+
+            // some cloudflare clients are using a refresh header
+            // Pull it out manually 
+            if (response.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable && response.Headers.Contains("Refresh"))
+            {
+                var refreshHeaders = response.Headers.GetValues("Refresh");
+                var redirval = "";
+                var redirtime = 0;
+                if (refreshHeaders != null)
+                {
+                    foreach (var value in refreshHeaders)
+                    {
+                        var start = value.IndexOf("=");
+                        var end = value.IndexOf(";");
+                        var len = value.Length;
+                        if (start > -1)
+                        {
+                            redirval = value.Substring(start + 1);
+                            result.RedirectingTo = redirval;
+                            // normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
+                            // of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
+                            // it shoudln't include service unavailable..only if we have this redirect header.
+                            response.StatusCode = System.Net.HttpStatusCode.Redirect;
+                            redirtime = Int32.Parse(value.Substring(0, end));
+                            System.Threading.Thread.Sleep(redirtime * 1000);
+                        }
+                    }
+                }
+            }
             if (response.Headers.Location != null)
             {
                 result.RedirectingTo = response.Headers.Location.ToString();
@@ -129,6 +168,7 @@ namespace Jackett.Utils.Clients
             // http://stackoverflow.com/questions/14681144/httpclient-not-storing-cookies-in-cookiecontainer
             IEnumerable<string> cookieHeaders;
             var responseCookies = new List<Tuple<string, string>>();
+
             if (response.Headers.TryGetValues("set-cookie", out cookieHeaders))
             {
                 foreach (var value in cookieHeaders)
diff --git a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
index 39eeec4e1aadbac75d6d477e5406b14ba02b59bd..1bb92ee0d9ea550284bc5ddbda776e8c15f53f16 100644
--- a/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
+++ b/src/Jackett/Utils/Clients/UnixLibCurlWebClient.cs
@@ -104,6 +104,28 @@ namespace Jackett.Utils.Clients
                         case "location":
                             result.RedirectingTo = header[1];
                             break;
+                        case "refresh":
+                            if (response.Status == System.Net.HttpStatusCode.ServiceUnavailable)
+                            {
+                                //"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
+                                var redirval = "";
+                                var value = header[1];
+                                var start = value.IndexOf("=");
+                                var end = value.IndexOf(";");
+                                var len = value.Length;
+                                if (start > -1)
+                                {
+                                    redirval = value.Substring(start + 1);
+                                    result.RedirectingTo = redirval;
+                                    // normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
+                                    // of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
+                                    // it shoudln't include service unavailable..only if we have this redirect header.
+                                    result.Status = System.Net.HttpStatusCode.Redirect;
+                                    var redirtime = Int32.Parse(value.Substring(0, end));
+                                    System.Threading.Thread.Sleep(redirtime * 1000);
+                                }
+                            }
+                            break;
                     }
                 }
             }
diff --git a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
index 7c124ddc5547a6a2dfc2dc66a620e044d3fbb364..cecef59eaa38158c0a71651f8c29d6ae07ddeb5b 100644
--- a/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
+++ b/src/Jackett/Utils/Clients/UnixSafeCurlWebClient.cs
@@ -50,6 +50,11 @@ namespace Jackett.Utils.Clients
         private async Task<WebClientByteResult> Run(WebRequest request)
         {
             var args = new StringBuilder();
+            if (Startup.ProxyConnection != null)
+            {
+                args.AppendFormat("-x " + Startup.ProxyConnection + " ");
+            }
+            
             args.AppendFormat("--url \"{0}\" ", request.Url);
            
             if (request.EmulateBrowser)
@@ -86,11 +91,16 @@ namespace Jackett.Utils.Clients
                 // https://git.fedorahosted.org/cgit/mod_nss.git/plain/docs/mod_nss.html
                 args.Append("--cipher " + SSLFix.CipherList);
             }
-
+            if (Startup.IgnoreSslErrors == true)
+            {
+                args.Append("-k ");
+            }
+            args.Append("-H \"Accept-Language: en-US,en\" ");
+            args.Append("-H \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\" ");
             string stdout = null;
             await Task.Run(() =>
             {
-                stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString(), true);
+                stdout = processService.StartProcessAndGetOutput(System.Environment.OSVersion.Platform == PlatformID.Unix ? "curl" : "curl.exe", args.ToString() , true);
             });
 
             var outputData = File.ReadAllBytes(tempFile);
@@ -101,6 +111,16 @@ namespace Jackett.Utils.Clients
             if (headSplit < 0)
                 throw new Exception("Invalid response");
             var headers = stdout.Substring(0, headSplit);
+            if (Startup.ProxyConnection != null)
+            {
+                // the proxy provided headers too so we need to split headers again
+                var headSplit1 = stdout.IndexOf("\r\n\r\n",headSplit + 4);
+                if (headSplit1 > 0)
+                {
+                    headers = stdout.Substring(headSplit + 4,headSplit1 - (headSplit + 4));
+                    headSplit = headSplit1;
+                }
+            }
             var headerCount = 0;
             var cookieBuilder = new StringBuilder();
             var cookies = new List<Tuple<string, string>>();
@@ -131,6 +151,24 @@ namespace Jackett.Utils.Clients
                             case "location":
                                 result.RedirectingTo = value.Trim();
                                 break;
+                            case "refresh":
+                                //"Refresh: 8;URL=/cdn-cgi/l/chk_jschl?pass=1451000679.092-1vJFUJLb9R"
+                                var redirval = "";
+                                var start = value.IndexOf("=");
+                                var end = value.IndexOf(";");
+                                var len = value.Length;
+                                if (start > -1)
+                                {
+                                    redirval = value.Substring(start + 1);
+                                    result.RedirectingTo = redirval;
+                                    // normally we don't want a serviceunavailable (503) to be a redirect, but that's the nature
+                                    // of this cloudflare approach..don't want to alter BaseWebResult.IsRedirect because normally
+                                    // it shoudln't include service unavailable..only if we have this redirect header.
+                                    result.Status = System.Net.HttpStatusCode.Redirect;
+                                    var redirtime = Int32.Parse(value.Substring(0, end));
+                                    System.Threading.Thread.Sleep(redirtime * 1000);
+                                }
+                                break;
                         }
                     }
                 }