From 12f5f8e4374a9744bf8d53012efd6f0facaba7f7 Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 8 Apr 2020 22:52:14 -0400 Subject: [PATCH] Convert Notifications from RestSharp to HttpClient --- .../Notifications/Boxcar/BoxcarProxy.cs | 34 +++--- .../Notifications/Discord/DiscordProxy.cs | 3 +- .../Notifications/Gotify/GotifyException.cs | 18 ++++ .../Notifications/Gotify/GotifyProxy.cs | 35 +++--- .../Gotify/InvalidResponseException.cs | 16 --- .../Notifications/Join/JoinProxy.cs | 42 ++++---- .../MediaBrowser/MediaBrowserService.cs | 4 +- .../Notifications/Prowl/Prowl.cs | 43 ++++++++ .../Notifications/Prowl/ProwlException.cs | 18 ++++ .../Notifications/Prowl/ProwlPriority.cs | 11 ++ .../Notifications/Prowl/ProwlProxy.cs | 78 ++++++++++++++ .../Notifications/Prowl/ProwlSettings.cs | 33 ++++++ .../PushBullet/PushBulletProxy.cs | 73 +++++++------ .../Notifications/Pushover/PushoverProxy.cs | 35 +++--- .../Notifications/Slack/SlackProxy.cs | 3 +- .../Subsonic/SubsonicServerProxy.cs | 49 ++++----- .../Notifications/Telegram/TelegramService.cs | 27 +++-- .../Notifications/Webhook/WebhookProxy.cs | 3 +- .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 82 ++++---------- src/NzbDrone.Core/Rest/IRestClientFactory.cs | 9 -- src/NzbDrone.Core/Rest/JsonNetSerializer.cs | 23 ---- src/NzbDrone.Core/Rest/RestClientFactory.cs | 34 ------ src/NzbDrone.Core/Rest/RestException.cs | 26 ----- src/NzbDrone.Core/Rest/RestSharpExtensions.cs | 101 ------------------ 24 files changed, 385 insertions(+), 415 deletions(-) create mode 100644 src/NzbDrone.Core/Notifications/Gotify/GotifyException.cs delete mode 100644 src/NzbDrone.Core/Notifications/Gotify/InvalidResponseException.cs create mode 100644 src/NzbDrone.Core/Notifications/Prowl/Prowl.cs create mode 100644 src/NzbDrone.Core/Notifications/Prowl/ProwlException.cs create mode 100644 src/NzbDrone.Core/Notifications/Prowl/ProwlPriority.cs create mode 100644 src/NzbDrone.Core/Notifications/Prowl/ProwlProxy.cs create mode 100644 src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs delete mode 100644 src/NzbDrone.Core/Rest/IRestClientFactory.cs delete mode 100644 src/NzbDrone.Core/Rest/JsonNetSerializer.cs delete mode 100644 src/NzbDrone.Core/Rest/RestClientFactory.cs delete mode 100644 src/NzbDrone.Core/Rest/RestException.cs delete mode 100644 src/NzbDrone.Core/Rest/RestSharpExtensions.cs diff --git a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs index deb9d02d2..775faa75d 100644 --- a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs +++ b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs @@ -3,8 +3,7 @@ using System.Net; using FluentValidation.Results; using NLog; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.Rest; -using RestSharp; +using NzbDrone.Common.Http; namespace NzbDrone.Core.Notifications.Boxcar { @@ -17,22 +16,20 @@ namespace NzbDrone.Core.Notifications.Boxcar public class BoxcarProxy : IBoxcarProxy { private const string URL = "https://new.boxcar.io/api/notifications"; - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public BoxcarProxy(IRestClientFactory restClientFactory, Logger logger) + public BoxcarProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } public void SendNotification(string title, string message, BoxcarSettings settings) { - var request = new RestRequest(Method.POST); - try { - SendNotification(title, message, request, settings); + ProcessNotification(title, message, settings); } catch (BoxcarException ex) { @@ -51,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Boxcar SendNotification(title, body, settings); return null; } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { @@ -69,21 +66,22 @@ namespace NzbDrone.Core.Notifications.Boxcar } } - private void SendNotification(string title, string message, RestRequest request, BoxcarSettings settings) + private void ProcessNotification(string title, string message, BoxcarSettings settings) { try { - var client = _restClientFactory.BuildClient(URL); + var requestBuilder = new HttpRequestBuilder(URL).Post(); - request.AddParameter("user_credentials", settings.Token); - request.AddParameter("notification[title]", title); - request.AddParameter("notification[long_message]", message); - request.AddParameter("notification[source_name]", BuildInfo.AppName); - request.AddParameter("notification[icon_url]", "https://github.com/lidarr/Lidarr/raw/develop/Logo/64.png"); + var request = requestBuilder.AddFormParameter("user_credentials", settings.Token) + .AddFormParameter("notification[title]", title) + .AddFormParameter("notification[long_message]", message) + .AddFormParameter("notification[source_name]", BuildInfo.AppName) + .AddFormParameter("notification[icon_url]", "https://raw.githubusercontent.com/Lidarr/Lidarr/develop/Logo/64.png") + .Build(); - client.ExecuteAndValidate(request); + _httpClient.Post(request); } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { diff --git a/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs b/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs index 37ecf794d..f3a6be3d2 100644 --- a/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs +++ b/src/NzbDrone.Core/Notifications/Discord/DiscordProxy.cs @@ -2,7 +2,6 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Notifications.Discord.Payloads; -using NzbDrone.Core.Rest; namespace NzbDrone.Core.Notifications.Discord { @@ -36,7 +35,7 @@ namespace NzbDrone.Core.Notifications.Discord _httpClient.Execute(request); } - catch (RestException ex) + catch (HttpException ex) { _logger.Error(ex, "Unable to post payload {0}", payload); throw new DiscordException("Unable to post payload", ex); diff --git a/src/NzbDrone.Core/Notifications/Gotify/GotifyException.cs b/src/NzbDrone.Core/Notifications/Gotify/GotifyException.cs new file mode 100644 index 000000000..35547460a --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Gotify/GotifyException.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Gotify +{ + public class GotifyException : NzbDroneException + { + public GotifyException(string message) + : base(message) + { + } + + public GotifyException(string message, Exception innerException, params object[] args) + : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Gotify/GotifyProxy.cs b/src/NzbDrone.Core/Notifications/Gotify/GotifyProxy.cs index 15a1c40d4..c61147296 100644 --- a/src/NzbDrone.Core/Notifications/Gotify/GotifyProxy.cs +++ b/src/NzbDrone.Core/Notifications/Gotify/GotifyProxy.cs @@ -1,5 +1,5 @@ -using NzbDrone.Core.Rest; -using RestSharp; +using System.Net; +using NzbDrone.Common.Http; namespace NzbDrone.Core.Notifications.Gotify { @@ -10,24 +10,35 @@ namespace NzbDrone.Core.Notifications.Gotify public class GotifyProxy : IGotifyProxy { - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; - public GotifyProxy(IRestClientFactory restClientFactory) + public GotifyProxy(IHttpClient httpClient) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; } public void SendNotification(string title, string message, GotifySettings settings) { - var client = _restClientFactory.BuildClient(settings.Server); - var request = new RestRequest("message", Method.POST); + try + { + var request = new HttpRequestBuilder(settings.Server).Post() + .AddFormParameter("token", settings.AppToken) + .AddFormParameter("title", title) + .AddFormParameter("message", message) + .AddFormParameter("priority", settings.Priority) + .Build(); - request.AddQueryParameter("token", settings.AppToken); - request.AddParameter("title", title); - request.AddParameter("message", message); - request.AddParameter("priority", settings.Priority); + _httpClient.Post(request); + } + catch (HttpException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new GotifyException("Unauthorized - AuthToken is invalid"); + } - client.ExecuteAndValidate(request); + throw new GotifyException("Unable to connect to Gotify. Status Code: {0}", ex); + } } } } diff --git a/src/NzbDrone.Core/Notifications/Gotify/InvalidResponseException.cs b/src/NzbDrone.Core/Notifications/Gotify/InvalidResponseException.cs deleted file mode 100644 index acdc931f9..000000000 --- a/src/NzbDrone.Core/Notifications/Gotify/InvalidResponseException.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace NzbDrone.Core.Notifications.Gotify -{ - public class InvalidResponseException : Exception - { - public InvalidResponseException() - { - } - - public InvalidResponseException(string message) - : base(message) - { - } - } -} diff --git a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs index 7ba3b467d..7a98dfda4 100644 --- a/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs +++ b/src/NzbDrone.Core/Notifications/Join/JoinProxy.cs @@ -2,9 +2,8 @@ using System; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Rest; -using RestSharp; namespace NzbDrone.Core.Notifications.Join { @@ -17,22 +16,22 @@ namespace NzbDrone.Core.Notifications.Join public class JoinProxy : IJoinProxy { private const string URL = "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?"; - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public JoinProxy(IRestClientFactory restClientFactory, Logger logger) + public JoinProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } public void SendNotification(string title, string message, JoinSettings settings) { - var request = new RestRequest(Method.GET); + var method = HttpMethod.GET; try { - SendNotification(title, message, request, settings); + SendNotification(title, message, method, settings); } catch (JoinException ex) { @@ -61,7 +60,7 @@ namespace NzbDrone.Core.Notifications.Join _logger.Error(ex, "Unable to send test Join message."); return new ValidationFailure("ApiKey", ex.Message); } - catch (RestException ex) + catch (HttpException ex) { _logger.Error(ex, "Unable to send test Join message. Server connection failed."); return new ValidationFailure("ApiKey", "Unable to connect to Join API. Please try again later."); @@ -73,31 +72,34 @@ namespace NzbDrone.Core.Notifications.Join } } - private void SendNotification(string title, string message, RestRequest request, JoinSettings settings) + private void SendNotification(string title, string message, HttpMethod method, JoinSettings settings) { - var client = _restClientFactory.BuildClient(URL); + var requestBuilder = new HttpRequestBuilder(URL); if (settings.DeviceNames.IsNotNullOrWhiteSpace()) { - request.AddParameter("deviceNames", settings.DeviceNames); + requestBuilder.AddQueryParam("deviceNames", settings.DeviceNames); } else if (settings.DeviceIds.IsNotNullOrWhiteSpace()) { - request.AddParameter("deviceIds", settings.DeviceIds); + requestBuilder.AddQueryParam("deviceIds", settings.DeviceIds); } else { - request.AddParameter("deviceId", "group.all"); + requestBuilder.AddQueryParam("deviceId", "group.all"); } - request.AddParameter("apikey", settings.ApiKey); - request.AddParameter("title", title); - request.AddParameter("text", message); - request.AddParameter("icon", "https://cdn.rawgit.com/Lidarr/Lidarr/develop/Logo/256.png"); // Use the Lidarr logo. - request.AddParameter("smallicon", "https://cdn.rawgit.com/Lidarr/Lidarr/develop/Logo/96-Outline-White.png"); // 96x96px with outline at 88x88px on a transparent background. - request.AddParameter("priority", settings.Priority); + var request = requestBuilder.AddQueryParam("apikey", settings.ApiKey) + .AddQueryParam("title", title) + .AddQueryParam("text", message) + .AddQueryParam("icon", "https://cdn.rawgit.com/Lidarr/Lidarr/develop/Logo/256.png") // Use the Lidarr logo. + .AddQueryParam("smallicon", "https://cdn.rawgit.com/Lidarr/Lidarr/develop/Logo/96-Outline-White.png") // 96x96px with outline at 88x88px on a transparent background. + .AddQueryParam("priority", settings.Priority) + .Build(); - var response = client.ExecuteAndValidate(request); + request.Method = method; + + var response = _httpClient.Execute(request); var res = Json.Deserialize(response.Content); if (res.success) diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs index f54c3b1bf..8a7216411 100644 --- a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Net; using FluentValidation.Results; using NLog; +using NzbDrone.Common.Http; using NzbDrone.Core.Music; -using NzbDrone.Core.Rest; namespace NzbDrone.Core.Notifications.Emby { @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Emby Notify(settings, "Test from Lidarr", "Success! MediaBrowser has been successfully configured!"); } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs new file mode 100644 index 000000000..bf8f1249a --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public class Prowl : NotificationBase + { + private readonly IProwlProxy _prowlProxy; + + public Prowl(IProwlProxy prowlProxy) + { + _prowlProxy = prowlProxy; + } + + public override string Link => "https://www.prowlapp.com/"; + public override string Name => "Prowl"; + + public override void OnGrab(GrabMessage message) + { + _prowlProxy.SendNotification(ALBUM_GRABBED_TITLE, message.Message, Settings.ApiKey); + } + + public override void OnReleaseImport(AlbumDownloadMessage message) + { + _prowlProxy.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings.ApiKey); + } + + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) + { + _prowlProxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings.ApiKey, (ProwlPriority)Settings.Priority); + } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_prowlProxy.Test(Settings)); + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlException.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlException.cs new file mode 100644 index 000000000..ee1a61df4 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlException.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public class ProwlException : NzbDroneException + { + public ProwlException(string message) + : base(message) + { + } + + public ProwlException(string message, Exception innerException, params object[] args) + : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlPriority.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlPriority.cs new file mode 100644 index 000000000..ef62f143f --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlPriority.cs @@ -0,0 +1,11 @@ +namespace NzbDrone.Core.Notifications.Prowl +{ + public enum ProwlPriority + { + VeryLow = -2, + Low = -1, + Normal = 0, + High = 1, + Emergency = 2 + } +} diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlProxy.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlProxy.cs new file mode 100644 index 000000000..c4453fd57 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlProxy.cs @@ -0,0 +1,78 @@ +using System; +using System.Net; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public interface IProwlProxy + { + void SendNotification(string title, string message, string apiKey, ProwlPriority priority = ProwlPriority.Normal, string url = null); + ValidationFailure Test(ProwlSettings settings); + } + + public class ProwlProxy : IProwlProxy + { + private const string PUSH_URL = "https://api.prowlapp.com/publicapi/add"; + private readonly IHttpClient _httpClient; + private readonly Logger _logger; + + public ProwlProxy(IHttpClient httpClient, Logger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public void SendNotification(string title, string message, string apiKey, ProwlPriority priority = ProwlPriority.Normal, string url = null) + { + try + { + var requestBuilder = new HttpRequestBuilder(PUSH_URL); + + var request = requestBuilder.Post() + .AddFormParameter("apikey", apiKey) + .AddFormParameter("application", BuildInfo.AppName) + .AddFormParameter("event", title) + .AddFormParameter("description", message) + .AddFormParameter("priority", priority) + .AddFormParameter("url", url) + .Build(); + + _httpClient.Post(request); + } + catch (HttpException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + { + _logger.Error(ex, "Apikey is invalid: {0}", apiKey); + throw new ProwlException("Apikey is invalid", ex); + } + + throw new ProwlException("Unable to send text message: " + ex.Message, ex); + } + catch (WebException ex) + { + throw new ProwlException("Failed to connect to prowl, please check your settings.", ex); + } + } + + public ValidationFailure Test(ProwlSettings settings) + { + try + { + const string title = "Test Notification"; + const string body = "This is a test message from Lidarr"; + + SendNotification(title, body, settings.ApiKey); + } + catch (Exception ex) + { + return new ValidationFailure("ApiKey", ex.Message); + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs new file mode 100644 index 000000000..21c5ec7b9 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs @@ -0,0 +1,33 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public class ProwlSettingsValidator : AbstractValidator + { + public ProwlSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + } + } + + public class ProwlSettings : IProviderConfig + { + private static readonly ProwlSettingsValidator Validator = new ProwlSettingsValidator(); + + [FieldDefinition(0, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://www.prowlapp.com/api_settings.php")] + public string ApiKey { get; set; } + + [FieldDefinition(1, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(ProwlPriority))] + public int Priority { get; set; } + + public bool IsValid => !string.IsNullOrWhiteSpace(ApiKey) && Priority >= -2 && Priority <= 2; + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs index fae2b2324..8ed8df0b7 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs @@ -5,10 +5,8 @@ using System.Net; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Rest; -using RestSharp; -using RestSharp.Authenticators; namespace NzbDrone.Core.Notifications.PushBullet { @@ -23,12 +21,12 @@ namespace NzbDrone.Core.Notifications.PushBullet { private const string PUSH_URL = "https://api.pushbullet.com/v2/pushes"; private const string DEVICE_URL = "https://api.pushbullet.com/v2/devices"; - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public PushBulletProxy(IRestClientFactory restClientFactory, Logger logger) + public PushBulletProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } @@ -98,15 +96,18 @@ namespace NzbDrone.Core.Notifications.PushBullet { try { - var client = _restClientFactory.BuildClient(DEVICE_URL); - var request = new RestRequest(Method.GET); + var requestBuilder = new HttpRequestBuilder(DEVICE_URL); - client.Authenticator = new HttpBasicAuthenticator(settings.ApiKey, string.Empty); - var response = client.ExecuteAndValidate(request); + var request = requestBuilder.Build(); + + request.Method = HttpMethod.GET; + request.AddBasicAuthentication(settings.ApiKey, string.Empty); + + var response = _httpClient.Execute(request); return Json.Deserialize(response.Content).Devices; } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { @@ -127,7 +128,7 @@ namespace NzbDrone.Core.Notifications.PushBullet SendNotification(title, body, settings); } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { @@ -147,50 +148,60 @@ namespace NzbDrone.Core.Notifications.PushBullet return null; } - private RestRequest BuildDeviceRequest(string deviceId) + private HttpRequestBuilder BuildDeviceRequest(string deviceId) { - var request = new RestRequest(Method.POST); + var requestBuilder = new HttpRequestBuilder(PUSH_URL).Post(); long integerId; + if (deviceId.IsNullOrWhiteSpace()) + { + return requestBuilder; + } + if (long.TryParse(deviceId, out integerId)) { - request.AddParameter("device_id", integerId); + requestBuilder.AddFormParameter("device_id", integerId); } else { - request.AddParameter("device_iden", deviceId); + requestBuilder.AddFormParameter("device_iden", deviceId); } - return request; + return requestBuilder; } - private RestRequest BuildChannelRequest(string channelTag) + private HttpRequestBuilder BuildChannelRequest(string channelTag) { - var request = new RestRequest(Method.POST); - request.AddParameter("channel_tag", channelTag); + var requestBuilder = new HttpRequestBuilder(PUSH_URL).Post(); - return request; + if (channelTag.IsNotNullOrWhiteSpace()) + { + requestBuilder.AddFormParameter("channel_tag", channelTag); + } + + return requestBuilder; } - private void SendNotification(string title, string message, RestRequest request, PushBulletSettings settings) + private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, PushBulletSettings settings) { try { - var client = _restClientFactory.BuildClient(PUSH_URL); - - request.AddParameter("type", "note"); - request.AddParameter("title", title); - request.AddParameter("body", message); + requestBuilder.AddFormParameter("type", "note") + .AddFormParameter("title", title) + .AddFormParameter("body", message); if (settings.SenderId.IsNotNullOrWhiteSpace()) { - request.AddParameter("source_device_iden", settings.SenderId); + requestBuilder.AddFormParameter("source_device_iden", settings.SenderId); } - client.Authenticator = new HttpBasicAuthenticator(settings.ApiKey, string.Empty); - client.ExecuteAndValidate(request); + var request = requestBuilder.Build(); + + request.AddBasicAuthentication(settings.ApiKey, string.Empty); + + _httpClient.Execute(request); } - catch (RestException ex) + catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { diff --git a/src/NzbDrone.Core/Notifications/Pushover/PushoverProxy.cs b/src/NzbDrone.Core/Notifications/Pushover/PushoverProxy.cs index 0b57684e1..ba13eb618 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/PushoverProxy.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/PushoverProxy.cs @@ -2,8 +2,7 @@ using System; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; -using NzbDrone.Core.Rest; -using RestSharp; +using NzbDrone.Common.Http; namespace NzbDrone.Core.Notifications.Pushover { @@ -16,38 +15,40 @@ namespace NzbDrone.Core.Notifications.Pushover public class PushoverProxy : IPushoverProxy { private const string URL = "https://api.pushover.net/1/messages.json"; - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public PushoverProxy(IRestClientFactory restClientFactory, Logger logger) + public PushoverProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } public void SendNotification(string title, string message, PushoverSettings settings) { - var client = _restClientFactory.BuildClient(URL); - var request = new RestRequest(Method.POST); - request.AddParameter("token", settings.ApiKey); - request.AddParameter("user", settings.UserKey); - request.AddParameter("device", string.Join(",", settings.Devices)); - request.AddParameter("title", title); - request.AddParameter("message", message); - request.AddParameter("priority", settings.Priority); + var requestBuilder = new HttpRequestBuilder(URL).Post(); + + requestBuilder.AddFormParameter("token", settings.ApiKey) + .AddFormParameter("user", settings.UserKey) + .AddFormParameter("device", string.Join(",", settings.Devices)) + .AddFormParameter("title", title) + .AddFormParameter("message", message) + .AddFormParameter("priority", settings.Priority); if ((PushoverPriority)settings.Priority == PushoverPriority.Emergency) { - request.AddParameter("retry", settings.Retry); - request.AddParameter("expire", settings.Expire); + requestBuilder.AddFormParameter("retry", settings.Retry); + requestBuilder.AddFormParameter("expire", settings.Expire); } if (!settings.Sound.IsNullOrWhiteSpace()) { - request.AddParameter("sound", settings.Sound); + requestBuilder.AddFormParameter("sound", settings.Sound); } - client.ExecuteAndValidate(request); + var request = requestBuilder.Build(); + + _httpClient.Post(request); } public ValidationFailure Test(PushoverSettings settings) diff --git a/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs b/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs index f510464d3..02075dad9 100644 --- a/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs +++ b/src/NzbDrone.Core/Notifications/Slack/SlackProxy.cs @@ -2,7 +2,6 @@ using NLog; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Notifications.Slack.Payloads; -using NzbDrone.Core.Rest; namespace NzbDrone.Core.Notifications.Slack { @@ -36,7 +35,7 @@ namespace NzbDrone.Core.Notifications.Slack _httpClient.Execute(request); } - catch (RestException ex) + catch (HttpException ex) { _logger.Error(ex, "Unable to post payload {0}", payload); throw new SlackExeption("Unable to post payload", ex); diff --git a/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs b/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs index f285b4a73..52f6dc048 100644 --- a/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Subsonic/SubsonicServerProxy.cs @@ -3,8 +3,6 @@ using System.Xml.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; -using NzbDrone.Core.Rest; -using RestSharp; namespace NzbDrone.Core.Notifications.Subsonic { @@ -18,12 +16,12 @@ namespace NzbDrone.Core.Notifications.Subsonic public class SubsonicServerProxy : ISubsonicServerProxy { - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public SubsonicServerProxy(IRestClientFactory restClientFactory, Logger logger) + public SubsonicServerProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } @@ -38,10 +36,10 @@ namespace NzbDrone.Core.Notifications.Subsonic public void Notify(SubsonicSettings settings, string message) { var resource = "addChatMessage"; - var request = GetSubsonicServerRequest(resource, Method.GET, settings); - request.AddParameter("message", message); - var client = GetSubsonicServerClient(settings); - var response = client.Execute(request); + var request = GetSubsonicServerRequest(resource, HttpMethod.GET, settings); + request.AddQueryParam("message", message); + + var response = _httpClient.Execute(request.Build()); _logger.Trace("Update response: {0}", response.Content); CheckForError(response, settings); @@ -50,9 +48,8 @@ namespace NzbDrone.Core.Notifications.Subsonic public void Update(SubsonicSettings settings) { var resource = "startScan"; - var request = GetSubsonicServerRequest(resource, Method.GET, settings); - var client = GetSubsonicServerClient(settings); - var response = client.Execute(request); + var request = GetSubsonicServerRequest(resource, HttpMethod.GET, settings); + var response = _httpClient.Execute(request.Build()); _logger.Trace("Update response: {0}", response.Content); CheckForError(response, settings); @@ -60,9 +57,8 @@ namespace NzbDrone.Core.Notifications.Subsonic public string Version(SubsonicSettings settings) { - var request = GetSubsonicServerRequest("ping", Method.GET, settings); - var client = GetSubsonicServerClient(settings); - var response = client.Execute(request); + var request = GetSubsonicServerRequest("ping", HttpMethod.GET, settings); + var response = _httpClient.Execute(request.Build()); _logger.Trace("Version response: {0}", response.Content); CheckForError(response, settings); @@ -78,27 +74,26 @@ namespace NzbDrone.Core.Notifications.Subsonic return version; } - private RestClient GetSubsonicServerClient(SubsonicSettings settings) + private HttpRequestBuilder GetSubsonicServerRequest(string resource, HttpMethod method, SubsonicSettings settings) { - return _restClientFactory.BuildClient(GetBaseUrl(settings, "rest")); - } + var client = new HttpRequestBuilder(GetBaseUrl(settings, "rest")); - private RestRequest GetSubsonicServerRequest(string resource, Method method, SubsonicSettings settings) - { - var request = new RestRequest(resource, method); + client.Resource(resource); if (settings.Username.IsNotNullOrWhiteSpace()) { - request.AddParameter("u", settings.Username); - request.AddParameter("p", settings.Password); - request.AddParameter("c", "Lidarr"); - request.AddParameter("v", "1.15.0"); + client.AddQueryParam("u", settings.Username) + .AddQueryParam("p", settings.Password) + .AddQueryParam("c", "Lidarr") + .AddQueryParam("v", "1.15.0"); } - return request; + client.Method = method; + + return client; } - private void CheckForError(IRestResponse response, SubsonicSettings settings) + private void CheckForError(HttpResponse response, SubsonicSettings settings) { _logger.Trace("Checking for error"); diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs index 15f3f5fca..be0ab5da6 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs @@ -4,9 +4,8 @@ using System.Web; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Rest; -using RestSharp; namespace NzbDrone.Core.Notifications.Telegram { @@ -19,12 +18,12 @@ namespace NzbDrone.Core.Notifications.Telegram public class TelegramProxy : ITelegramProxy { private const string URL = "https://api.telegram.org"; - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public TelegramProxy(IRestClientFactory restClientFactory, Logger logger) + public TelegramProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } @@ -32,17 +31,17 @@ namespace NzbDrone.Core.Notifications.Telegram { //Format text to add the title before and bold using markdown var text = $"{HttpUtility.HtmlEncode(title)}\n{HttpUtility.HtmlEncode(message)}"; - var client = _restClientFactory.BuildClient(URL); - var request = new RestRequest("bot{token}/sendmessage", Method.POST); + var requestBuilder = new HttpRequestBuilder(URL).Resource("bot{token}/sendmessage").Post(); - request.AddUrlSegment("token", settings.BotToken); - request.AddParameter("chat_id", settings.ChatId); - request.AddParameter("parse_mode", "HTML"); - request.AddParameter("text", text); - request.AddParameter("disable_notification", settings.SendSilently); + var request = requestBuilder.SetSegment("token", settings.BotToken) + .AddFormParameter("chat_id", settings.ChatId) + .AddFormParameter("parse_mode", "HTML") + .AddFormParameter("text", text) + .AddFormParameter("disable_notification", settings.SendSilently) + .Build(); - client.ExecuteAndValidate(request); + _httpClient.Post(request); } public ValidationFailure Test(TelegramSettings settings) @@ -62,7 +61,7 @@ namespace NzbDrone.Core.Notifications.Telegram { return new ValidationFailure("Connection", $"{webException.Status.ToString()}: {webException.Message}"); } - else if (ex is RestException restException && restException.Response.StatusCode == HttpStatusCode.BadRequest) + else if (ex is Common.Http.HttpException restException && restException.Response.StatusCode == HttpStatusCode.BadRequest) { var error = Json.Deserialize(restException.Response.Content); var property = error.Description.ContainsIgnoreCase("chat not found") ? "ChatId" : "BotToken"; diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs index f7eedec69..489eeb41f 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookProxy.cs @@ -1,7 +1,6 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Rest; namespace NzbDrone.Core.Notifications.Webhook { @@ -38,7 +37,7 @@ namespace NzbDrone.Core.Notifications.Webhook _httpClient.Execute(request); } - catch (RestException ex) + catch (HttpException ex) { throw new WebhookException("Unable to post to webhook: {0}", ex, ex.Message); } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs index 5735aee7a..4167a5f7b 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Notifications.Xbmc.Model; -using NzbDrone.Core.Rest; -using RestSharp; -using RestSharp.Authenticators; namespace NzbDrone.Core.Notifications.Xbmc { @@ -21,108 +19,74 @@ namespace NzbDrone.Core.Notifications.Xbmc public class XbmcJsonApiProxy : IXbmcJsonApiProxy { - private readonly IRestClientFactory _restClientFactory; + private readonly IHttpClient _httpClient; private readonly Logger _logger; - public XbmcJsonApiProxy(IRestClientFactory restClientFactory, Logger logger) + public XbmcJsonApiProxy(IHttpClient httpClient, Logger logger) { - _restClientFactory = restClientFactory; + _httpClient = httpClient; _logger = logger; } public string GetJsonVersion(XbmcSettings settings) { - var request = new RestRequest(); - return ProcessRequest(request, settings, "JSONRPC.Version"); + return ProcessRequest(settings, "JSONRPC.Version"); } public void Notify(XbmcSettings settings, string title, string message) { - var request = new RestRequest(); - - var parameters = new Dictionary(); - parameters.Add("title", title); - parameters.Add("message", message); - parameters.Add("image", "https://raw.github.com/Lidarr/Lidarr/develop/Logo/64.png"); - parameters.Add("displaytime", settings.DisplayTime * 1000); - - ProcessRequest(request, settings, "GUI.ShowNotification", parameters); + ProcessRequest(settings, "GUI.ShowNotification", title, message, "https://raw.github.com/Lidarr/Lidarr/develop/Logo/64.png", settings.DisplayTime * 1000); } public string UpdateLibrary(XbmcSettings settings, string path) { - var request = new RestRequest(); - var parameters = new Dictionary(); - parameters.Add("directory", path); - - if (path.IsNullOrWhiteSpace()) - { - parameters = null; - } - - var response = ProcessRequest(request, settings, "AudioLibrary.Scan", parameters); + var response = ProcessRequest(settings, "AudioLibrary.Scan", path); return Json.Deserialize>(response).Result; } public void CleanLibrary(XbmcSettings settings) { - var request = new RestRequest(); - - ProcessRequest(request, settings, "AudioLibrary.Clean"); + ProcessRequest(settings, "AudioLibrary.Clean"); } public List GetActivePlayers(XbmcSettings settings) { - var request = new RestRequest(); - - var response = ProcessRequest(request, settings, "Player.GetActivePlayers"); + var response = ProcessRequest(settings, "Player.GetActivePlayers"); return Json.Deserialize(response).Result; } public List GetArtist(XbmcSettings settings) { - var request = new RestRequest(); - var parameters = new Dictionary(); - parameters.Add("properties", new[] { "musicbrainzartistid" }); //TODO: Figure out why AudioLibrary doesnt list file location like videoLibray - - var response = ProcessRequest(request, settings, "AudioLibrary.GetArtists", parameters); + var response = ProcessRequest(settings, "AudioLibrary.GetArtists", new[] { "properties", "musicbrainzartistid" }); return Json.Deserialize(response).Result.Artists; } - private string ProcessRequest(IRestRequest request, XbmcSettings settings, string method, Dictionary parameters = null) + private string ProcessRequest(XbmcSettings settings, string method, params object[] parameters) { - var client = BuildClient(settings); - - request.Method = Method.POST; - request.RequestFormat = DataFormat.Json; - request.JsonSerializer = new JsonNetSerializer(); - request.AddBody(new { jsonrpc = "2.0", method = method, id = 10, @params = parameters }); - - var response = client.ExecuteAndValidate(request); - _logger.Trace("Response: {0}", response.Content); - - CheckForError(response); + var url = string.Format(@"http://{0}/jsonrpc", settings.Address); + var requestBuilder = new JsonRpcRequestBuilder(url, method, parameters); - return response.Content; - } + requestBuilder.LogResponseContent = true; - private IRestClient BuildClient(XbmcSettings settings) - { - var url = string.Format(@"http://{0}/jsonrpc", settings.Address); - var client = _restClientFactory.BuildClient(url); + var request = requestBuilder.Build(); if (!settings.Username.IsNullOrWhiteSpace()) { - client.Authenticator = new HttpBasicAuthenticator(settings.Username, settings.Password); + request.AddBasicAuthentication(settings.Username, settings.Password); } - return client; + var response = _httpClient.Execute(request); + _logger.Trace("Response: {0}", response.Content); + + CheckForError(response); + + return response.Content; } - private void CheckForError(IRestResponse response) + private void CheckForError(HttpResponse response) { if (string.IsNullOrWhiteSpace(response.Content)) { diff --git a/src/NzbDrone.Core/Rest/IRestClientFactory.cs b/src/NzbDrone.Core/Rest/IRestClientFactory.cs deleted file mode 100644 index 84547fab1..000000000 --- a/src/NzbDrone.Core/Rest/IRestClientFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using RestSharp; - -namespace NzbDrone.Core.Rest -{ - public interface IRestClientFactory - { - RestClient BuildClient(string baseUrl); - } -} diff --git a/src/NzbDrone.Core/Rest/JsonNetSerializer.cs b/src/NzbDrone.Core/Rest/JsonNetSerializer.cs deleted file mode 100644 index e5bcdfb3a..000000000 --- a/src/NzbDrone.Core/Rest/JsonNetSerializer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using NzbDrone.Common.Serializer; -using RestSharp.Serializers; - -namespace NzbDrone.Core.Rest -{ - public class JsonNetSerializer : ISerializer - { - public JsonNetSerializer() - { - ContentType = "application/json"; - } - - public string Serialize(object obj) - { - return obj.ToJson(); - } - - public string RootElement { get; set; } - public string Namespace { get; set; } - public string DateFormat { get; set; } - public string ContentType { get; set; } - } -} diff --git a/src/NzbDrone.Core/Rest/RestClientFactory.cs b/src/NzbDrone.Core/Rest/RestClientFactory.cs deleted file mode 100644 index 2614840c8..000000000 --- a/src/NzbDrone.Core/Rest/RestClientFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Common.Http.Proxy; -using RestSharp; - -namespace NzbDrone.Core.Rest -{ - public class RestClientFactory : IRestClientFactory - { - private readonly IHttpProxySettingsProvider _httpProxySettingsProvider; - private readonly ICreateManagedWebProxy _createManagedWebProxy; - - public RestClientFactory(IHttpProxySettingsProvider httpProxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy) - { - _httpProxySettingsProvider = httpProxySettingsProvider; - _createManagedWebProxy = createManagedWebProxy; - } - - public RestClient BuildClient(string baseUrl) - { - var restClient = new RestClient(baseUrl) - { - UserAgent = $"{BuildInfo.AppName}/{BuildInfo.Version} ({OsInfo.Os})" - }; - - var proxySettings = _httpProxySettingsProvider.GetProxySettings(); - if (proxySettings != null) - { - restClient.Proxy = _createManagedWebProxy.GetWebProxy(proxySettings); - } - - return restClient; - } - } -} diff --git a/src/NzbDrone.Core/Rest/RestException.cs b/src/NzbDrone.Core/Rest/RestException.cs deleted file mode 100644 index 99a0be97a..000000000 --- a/src/NzbDrone.Core/Rest/RestException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using RestSharp; - -namespace NzbDrone.Core.Rest -{ - public class RestException : Exception - { - public IRestResponse Response { get; private set; } - - public RestException(IRestResponse response, IRestClient restClient) - : base(string.Format("REST request failed: [{0}] [{1}] at [{2}]", (int)response.StatusCode, response.Request.Method, restClient.BuildUri(response.Request))) - { - Response = response; - } - - public override string ToString() - { - if (Response != null) - { - return base.ToString() + Environment.NewLine + Response.Content; - } - - return base.ToString(); - } - } -} diff --git a/src/NzbDrone.Core/Rest/RestSharpExtensions.cs b/src/NzbDrone.Core/Rest/RestSharpExtensions.cs deleted file mode 100644 index 938b8723d..000000000 --- a/src/NzbDrone.Core/Rest/RestSharpExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Linq; -using System.Net; -using NLog; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Instrumentation; -using NzbDrone.Common.Serializer; -using RestSharp; - -namespace NzbDrone.Core.Rest -{ - public static class RestSharpExtensions - { - private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(RestSharpExtensions)); - - public static IRestResponse ValidateResponse(this IRestResponse response, IRestClient restClient) - { - Ensure.That(response, () => response).IsNotNull(); - - if (response.Request == null && response.ErrorException != null) - { - throw response.ErrorException; - } - - Ensure.That(response.Request, () => response.Request).IsNotNull(); - Ensure.That(restClient, () => restClient).IsNotNull(); - - Logger.Debug("Validating Responses from [{0}] [{1}] status: [{2}]", response.Request.Method, restClient.BuildUri(response.Request), response.StatusCode); - - if (response.ResponseUri == null) - { - Logger.Error(response.ErrorException, "Error communicating with server"); - throw response.ErrorException; - } - - switch (response.StatusCode) - { - case HttpStatusCode.OK: - { - return response; - } - - case HttpStatusCode.NoContent: - { - return response; - } - - case HttpStatusCode.Created: - { - return response; - } - - default: - { - Logger.Warn("[{0}] [{1}] Failed. [{2}]", response.Request.Method, response.ResponseUri.ToString(), response.StatusCode); - throw new RestException(response, restClient); - } - } - } - - public static T Read(this IRestResponse restResponse, IRestClient restClient) - where T : class, new() - { - restResponse.ValidateResponse(restClient); - - if (restResponse.Content != null) - { - Logger.Trace("Response: " + restResponse.Content); - } - - return Json.Deserialize(restResponse.Content); - } - - public static T ExecuteAndValidate(this IRestClient client, IRestRequest request) - where T : class, new() - { - return client.Execute(request).Read(client); - } - - public static IRestResponse ExecuteAndValidate(this IRestClient client, IRestRequest request) - { - return client.Execute(request).ValidateResponse(client); - } - - public static void AddQueryString(this IRestRequest request, string name, object value) - { - request.AddParameter(name, value.ToString(), ParameterType.GetOrPost); - } - - public static object GetHeaderValue(this IRestResponse response, string key) - { - var header = response.Headers.FirstOrDefault(v => v.Name == key); - - if (header == null) - { - return null; - } - - return header.Value; - } - } -}