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; } } }