diff --git a/src/Jackett/ConfigurationData.cs b/src/Jackett/ConfigurationData.cs index 00a9b8801..971698b0c 100644 --- a/src/Jackett/ConfigurationData.cs +++ b/src/Jackett/ConfigurationData.cs @@ -73,19 +73,40 @@ namespace Jackett public string ID { get { return Name.Replace(" ", "").ToLower(); } } } + public class DisplayItem : StringItem + { + public DisplayItem(string value) + { + Value = value; + ItemType = ItemType.DisplayInfo; + } + } + public class StringItem : Item { public string Value { get; set; } + public StringItem() + { + ItemType = ConfigurationData.ItemType.InputString; + } } public class BoolItem : Item { public bool Value { get; set; } + public BoolItem() + { + ItemType = ConfigurationData.ItemType.InputBool; + } } public class ImageItem : Item { public byte[] Value { get; set; } + public ImageItem() + { + ItemType = ConfigurationData.ItemType.DisplayImage; + } } public abstract Item[] GetItems(); diff --git a/src/Jackett/ConfigurationDataBasicLogin.cs b/src/Jackett/ConfigurationDataBasicLogin.cs index c8649bffc..19a2ab0ff 100644 --- a/src/Jackett/ConfigurationDataBasicLogin.cs +++ b/src/Jackett/ConfigurationDataBasicLogin.cs @@ -13,8 +13,8 @@ namespace Jackett public ConfigurationDataBasicLogin() { - Username = new StringItem { Name = "Username", ItemType = ItemType.InputString }; - Password = new StringItem { Name = "Password", ItemType = ItemType.InputString }; + Username = new StringItem { Name = "Username" }; + Password = new StringItem { Name = "Password" }; } public override Item[] GetItems() diff --git a/src/Jackett/Indexers/BitMeTV.cs b/src/Jackett/Indexers/BitMeTV.cs index 9627fa997..d32bdcc8e 100644 --- a/src/Jackett/Indexers/BitMeTV.cs +++ b/src/Jackett/Indexers/BitMeTV.cs @@ -24,10 +24,10 @@ namespace Jackett public BmtvConfig() { - Username = new StringItem { Name = "Username", ItemType = ItemType.InputString }; - Password = new StringItem { Name = "Password", ItemType = ItemType.InputString }; - CaptchaImage = new ImageItem { Name = "Captcha Image", ItemType = ItemType.DisplayImage }; - CaptchaText = new StringItem { Name = "Captcha Text", ItemType = ItemType.InputString }; + Username = new StringItem { Name = "Username" }; + Password = new StringItem { Name = "Password" }; + CaptchaImage = new ImageItem { Name = "Captcha Image" }; + CaptchaText = new StringItem { Name = "Captcha Text" }; } public override Item[] GetItems() diff --git a/src/Jackett/Indexers/ThePirateBay.cs b/src/Jackett/Indexers/ThePirateBay.cs index f707e69df..baa849c8a 100644 --- a/src/Jackett/Indexers/ThePirateBay.cs +++ b/src/Jackett/Indexers/ThePirateBay.cs @@ -22,7 +22,7 @@ namespace Jackett.Indexers public ThePirateBayConfig() { - Url = new StringItem { Name = "Url", ItemType = ItemType.InputString, Value = "https://thepiratebay.se/" }; + Url = new StringItem { Name = "Url", Value = "https://thepiratebay.se/" }; } public override Item[] GetItems() diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 908c55ad0..56eae05e6 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -90,6 +90,7 @@ + diff --git a/src/Jackett/Server.cs b/src/Jackett/Server.cs index 1b7234c8f..faf0b9ee6 100644 --- a/src/Jackett/Server.cs +++ b/src/Jackett/Server.cs @@ -16,13 +16,15 @@ namespace Jackett HttpListener listener; IndexerManager indexerManager; WebApi webApi; + SonarrApi sonarrApi; public Server() { LoadApiKey(); indexerManager = new IndexerManager(); - webApi = new WebApi(indexerManager); + sonarrApi = new SonarrApi(); + webApi = new WebApi(indexerManager, sonarrApi); listener = new HttpListener(); listener.Prefixes.Add("http://*:9117/"); diff --git a/src/Jackett/SonarApi.cs b/src/Jackett/SonarApi.cs new file mode 100644 index 000000000..33fb362aa --- /dev/null +++ b/src/Jackett/SonarApi.cs @@ -0,0 +1,122 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett +{ + public class SonarrApi + { + public class ConfigurationSonarr : ConfigurationData + { + public StringItem Host { get; private set; } + public StringItem Port { get; private set; } + public StringItem ApiKey { get; private set; } + + DisplayItem ApiInfo; + + public ConfigurationSonarr() + { + Host = new StringItem { Name = "Host", Value = "http://localhost" }; + Port = new StringItem { Name = "Port", Value = "8989" }; + ApiKey = new StringItem { Name = "API Key" }; + ApiInfo = new DisplayItem("API Key can be found in Sonarr > Settings > General > Security") { Name = "API Info" }; + } + + public override Item[] GetItems() + { + return new Item[] { Host, Port, ApiKey, ApiInfo }; + } + + } + + static string SonarrConfigFile = Path.Combine(Program.AppConfigDirectory, "sonarr_api.json"); + + string Host; + int Port; + string ApiKey; + + CookieContainer cookies; + HttpClientHandler handler; + HttpClient client; + + public SonarrApi() + { + LoadSettings(); + + cookies = new CookieContainer(); + handler = new HttpClientHandler + { + CookieContainer = cookies, + AllowAutoRedirect = true, + UseCookies = true, + }; + client = new HttpClient(handler); + } + + void LoadSettings() + { + try + { + if (File.Exists(SonarrConfigFile)) + { + var json = JObject.Parse(File.ReadAllText(SonarrConfigFile)); + Host = (string)json["host"]; + Port = (int)json["port"]; + ApiKey = (string)json["api_key"]; + } + } + catch (Exception) { } + } + + void SaveSettings() + { + JObject json = new JObject(); + json["host"] = Host; + json["port"] = Port; + json["api_key"] = ApiKey; + File.WriteAllText(SonarrConfigFile, json.ToString()); + } + + public ConfigurationSonarr GetConfiguration() + { + var config = new ConfigurationSonarr(); + if (ApiKey != null) + { + config.Host.Value = Host; + config.Port.Value = Port.ToString(); + config.ApiKey.Value = ApiKey; + } + return config; + } + + public async Task ApplyConfiguration(JToken configJson) + { + var config = new ConfigurationSonarr(); + config.LoadValuesFromJson(configJson); + await TestConnection(config.Host.Value, int.Parse(config.Port.Value), config.ApiKey.Value); + Host = "http://" + new Uri(config.Host.Value).Host; + Port = int.Parse(config.Port.Value); + ApiKey = config.ApiKey.Value; + SaveSettings(); + } + + public async Task TestConnection() + { + await TestConnection(Host, Port, ApiKey); + } + + async Task TestConnection(string host, int port, string apiKey) + { + Uri hostUri = new Uri(host); + var queryUrl = string.Format("http://{0}:{1}/api/series?apikey={2}", hostUri.Host, port, apiKey); + var response = await client.GetStringAsync(queryUrl); + var json = JArray.Parse(response); + } + } +} diff --git a/src/Jackett/WebApi.cs b/src/Jackett/WebApi.cs index 5072b7ee4..fc3a0e8d9 100644 --- a/src/Jackett/WebApi.cs +++ b/src/Jackett/WebApi.cs @@ -22,7 +22,10 @@ namespace Jackett ConfigureIndexer, GetIndexers, TestIndexer, - DeleteIndexer + DeleteIndexer, + GetSonarrConfig, + ApplySonarrConfig, + TestSonarr } static Dictionary WebApiMethods = new Dictionary { @@ -30,14 +33,19 @@ namespace Jackett { "configure_indexer", WebApiMethod.ConfigureIndexer }, { "get_indexers", WebApiMethod.GetIndexers }, { "test_indexer", WebApiMethod.TestIndexer }, - { "delete_indexer", WebApiMethod.DeleteIndexer } + { "delete_indexer", WebApiMethod.DeleteIndexer }, + { "get_sonarr_config", WebApiMethod.GetSonarrConfig }, + { "apply_sonarr_config", WebApiMethod.ApplySonarrConfig }, + { "test_sonarr", WebApiMethod.TestSonarr } }; IndexerManager indexerManager; + SonarrApi sonarrApi; - public WebApi(IndexerManager indexerManager) + public WebApi(IndexerManager indexerManager, SonarrApi sonarrApi) { this.indexerManager = indexerManager; + this.sonarrApi = sonarrApi; } public bool HandleRequest(HttpListenerContext context) @@ -110,6 +118,15 @@ namespace Jackett case WebApiMethod.DeleteIndexer: handlerTask = HandleDeleteIndexer; break; + case WebApiMethod.GetSonarrConfig: + handlerTask = HandleGetSonarrConfig; + break; + case WebApiMethod.ApplySonarrConfig: + handlerTask = HandleApplySonarrConfig; + break; + case WebApiMethod.TestSonarr: + handlerTask = HandleTestSonarr; + break; default: handlerTask = HandleInvalidApiMethod; break; @@ -125,15 +142,61 @@ namespace Jackett context.Response.OutputStream.Close(); } + async Task HandleTestSonarr(HttpListenerContext context) + { + JToken jsonReply = new JObject(); + try + { + await sonarrApi.TestConnection(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return jsonReply; + } + + async Task HandleApplySonarrConfig(HttpListenerContext context) + { + JToken jsonReply = new JObject(); + try + { + var postData = await ReadPostDataJson(context.Request.InputStream); + await sonarrApi.ApplyConfiguration(postData); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return jsonReply; + } + + Task HandleGetSonarrConfig(HttpListenerContext context) + { + JObject jsonReply = new JObject(); + try + { + jsonReply["config"] = sonarrApi.GetConfiguration().ToJson(); + jsonReply["result"] = "success"; + } + catch (Exception ex) + { + jsonReply["result"] = "error"; + jsonReply["error"] = ex.Message; + } + return Task.FromResult(jsonReply); + } + Task HandleInvalidApiMethod(HttpListenerContext context) { - return Task.Run(() => - { - JToken jsonReply = new JObject(); - jsonReply["result"] = "error"; - jsonReply["error"] = "Invalid API method"; - return jsonReply; - }); + JToken jsonReply = new JObject(); + jsonReply["result"] = "error"; + jsonReply["error"] = "Invalid API method"; + return Task.FromResult(jsonReply); } async Task HandleConfigForm(HttpListenerContext context) diff --git a/src/Jackett/WebContent/index.html b/src/Jackett/WebContent/index.html index 2a384e73a..d94f64630 100644 --- a/src/Jackett/WebContent/index.html +++ b/src/Jackett/WebContent/index.html @@ -22,7 +22,7 @@ #page { border-radius: 6px; background-color: white; - max-width: 800px; + max-width: 900px; margin: 0 auto; margin-top: 30px; padding: 20px; @@ -35,26 +35,15 @@ display: none; } - #api-key-input { - width: 300px; - display: inline-block; - font-family: monospace; - } - - #api-key-header { - font-size: 20px; - } - .card { background-color: #f9f9f9; border-radius: 6px; box-shadow: 1px 1px 5px 2px #cdcdcd; padding: 10px; - width: 220px; + width: 260px; display: inline-block; - margin-right: 30px; vertical-align: top; - margin-bottom: 30px; + margin: 10px; } .unconfigured-indexer { @@ -62,11 +51,12 @@ } .indexer { - height: 265px; + height: 230px; } #add-indexer { margin-right: 0px; + border: 0; } .indexer-logo { @@ -87,13 +77,13 @@ } .indexer-buttons > .btn { - width: 90px; margin-bottom: 10px; } - .indexer-buttons > .btn:nth-child(even) { - margin-left: 5px; - } + + .indexer-button-test { + width: 60px; + } .indexer-add-content { color: gray; @@ -161,18 +151,67 @@ #setup-indexer-go { width: 70px; } + + hr { + border-top-color: #cdcdcd; + } + + .input-area { + } + + .input-area > * { + vertical-align: middle; + } + + .input-area > p { + margin-top: 10px; + } + + .input-header { + font-size: 18px; + width: 140px; + display: inline-block; + } + + .input-right { + width: 300px; + display: inline-block; + font-family: monospace; + } + + #sonarr-warning { + display: none; + } + need a logo.. - - need a logo.. - API Key: - - (Same key works for all indexers) + + + + Sonarr API Host: + + + Settings + + + Test + + + + Sonarr API must be configured + + + + + + + Jackett API Key: + + Use this key when adding indexers to Sonarr. This key works for all indexers. - @@ -208,42 +247,44 @@ - - - - - × - Setup indexer - - - - -
+ + Sonarr API must be configured +
Use this key when adding indexers to Sonarr. This key works for all indexers.