From 1c4ba992285394f905225faf0705ae68f79f627e Mon Sep 17 00:00:00 2001 From: Dan Done Date: Mon, 14 Feb 2022 08:20:07 +0000 Subject: [PATCH] #52 Synology Chat WIP. Messaging working, but looks like the config will need a reference to SynoAI's URL to get the images from the ImageController. --- SynoAI/Controllers/ImageController.cs | 4 +- SynoAI/Notifiers/NotifierFactory.cs | 4 + SynoAI/Notifiers/NotifierType.cs | 6 +- SynoAI/Notifiers/SynologyChat/SynologyChat.cs | 83 +++++++++++++++++++ .../SynologyChatErrorReasonResponse.cs | 17 ++++ .../SynologyChat/SynologyChatErrorResponse.cs | 13 +++ .../SynologyChat/SynologyChatFactory.cs | 25 ++++++ .../SynologyChat/SynologyChatResponse.cs | 12 +++ 8 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 SynoAI/Notifiers/SynologyChat/SynologyChat.cs create mode 100644 SynoAI/Notifiers/SynologyChat/SynologyChatErrorReasonResponse.cs create mode 100644 SynoAI/Notifiers/SynologyChat/SynologyChatErrorResponse.cs create mode 100644 SynoAI/Notifiers/SynologyChat/SynologyChatFactory.cs create mode 100644 SynoAI/Notifiers/SynologyChat/SynologyChatResponse.cs diff --git a/SynoAI/Controllers/ImageController.cs b/SynoAI/Controllers/ImageController.cs index cdbcdae..c40e09b 100644 --- a/SynoAI/Controllers/ImageController.cs +++ b/SynoAI/Controllers/ImageController.cs @@ -6,9 +6,7 @@ namespace SynoAI.Controllers public class ImageController : Controller { /// - /// Return snapshot image as JPEG, either in original size or a scaled down version, if asked. - //// In order to use System.Drawing.Common - //// In Terminal, issue: dotnet add SynoAI package System.Drawing.Common + /// Returns the file for the specified camera. /// [Route("Image/{cameraName}/{filename}")] public ActionResult Get(string cameraName, string filename) diff --git a/SynoAI/Notifiers/NotifierFactory.cs b/SynoAI/Notifiers/NotifierFactory.cs index 1a24fe8..3621388 100644 --- a/SynoAI/Notifiers/NotifierFactory.cs +++ b/SynoAI/Notifiers/NotifierFactory.cs @@ -3,6 +3,7 @@ using SynoAI.Notifiers.Email; using SynoAI.Notifiers.Pushbullet; using SynoAI.Notifiers.Pushover; +using SynoAI.Notifiers.SynologyChat; using SynoAI.Notifiers.Telegram; using SynoAI.Notifiers.Webhook; using System; @@ -31,6 +32,9 @@ public static INotifier Create(NotifierType type, ILogger logger, IConfiguration case NotifierType.Pushover: factory = new PushoverFactory(); break; + case NotifierType.SynologyChat: + factory = new SynologyChatFactory(); + break; case NotifierType.Telegram: factory = new TelegramFactory(); break; diff --git a/SynoAI/Notifiers/NotifierType.cs b/SynoAI/Notifiers/NotifierType.cs index b264da0..639367d 100644 --- a/SynoAI/Notifiers/NotifierType.cs +++ b/SynoAI/Notifiers/NotifierType.cs @@ -19,12 +19,16 @@ public enum NotifierType /// Pushover, /// + /// Sends a notification message and image using the SynologyChat API. + /// + SynologyChat, + /// /// Sends a notification to Telegram with the image attached. /// Telegram, /// /// Calls a webhook with the image attached. /// - Webhook + Webhook, } } diff --git a/SynoAI/Notifiers/SynologyChat/SynologyChat.cs b/SynoAI/Notifiers/SynologyChat/SynologyChat.cs new file mode 100644 index 0000000..fd58f52 --- /dev/null +++ b/SynoAI/Notifiers/SynologyChat/SynologyChat.cs @@ -0,0 +1,83 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SynoAI.Models; +using SynoAI.Services; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Threading.Tasks; + +namespace SynoAI.Notifiers.SynologyChat +{ + /// + /// Calls a third party API. + /// + public class SynologyChat : NotifierBase + { + /// + /// The URL to send the request to including the token. + /// + public string Url { get; set; } + + /// + /// Sends a notification to the Webhook. + /// + /// The camera that triggered the notification. + /// The notification data to process. + /// A logger. + public override async Task SendAsync(Camera camera, Notification notification, ILogger logger) + { + logger.LogInformation($"{camera.Name}: SynologyChat: Processing"); + using (HttpClient client = new()) + { + IEnumerable foundTypes = notification.FoundTypes; + string message = GetMessage(camera, foundTypes); + + var request = new + { + text = message, + file_url = new Uri(new Uri(Config.Url), new Uri($"Image/{camera.Name}/{notification.ProcessedImage.FileName}", UriKind.Relative)) + }; + + string requestJson = JsonConvert.SerializeObject(request); + Dictionary payload = new() + { + { "payload", requestJson }, + }; + + using (FormUrlEncodedContent content = new(payload)) + { + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + + logger.LogInformation($"{camera.Name}: SynologyChat: POSTing message."); + + HttpResponseMessage response = await client.PostAsync(Url, content); + if (response.IsSuccessStatusCode) + { + // Check that it's actually successful, because Synology like to make things awkward + string responseString = await response.Content.ReadAsStringAsync(); + SynologyChatResponse actualResponse = JsonConvert.DeserializeObject(responseString); + if (actualResponse.Success) + { + logger.LogInformation($"{camera.Name}: SynologyChat: Success."); + } + else + { + logger.LogInformation($"{camera.Name}: SynologyChat: Failed with error '{actualResponse.Error.Code}': {actualResponse.Error.Errors}."); + } + } + else + { + logger.LogWarning($"{camera.Name}: SynologyChat: The end point responded with HTTP status code '{response.StatusCode}'."); + } + } + } + } + } +} diff --git a/SynoAI/Notifiers/SynologyChat/SynologyChatErrorReasonResponse.cs b/SynoAI/Notifiers/SynologyChat/SynologyChatErrorReasonResponse.cs new file mode 100644 index 0000000..37c815c --- /dev/null +++ b/SynoAI/Notifiers/SynologyChat/SynologyChatErrorReasonResponse.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace SynoAI.Notifiers.SynologyChat +{ + public class SynologyChatErrorReasonResponse + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("reason")] + public string Reason { get; set; } + + public override string ToString() + { + return $"{Name} ({Reason})"; + } + } +} diff --git a/SynoAI/Notifiers/SynologyChat/SynologyChatErrorResponse.cs b/SynoAI/Notifiers/SynologyChat/SynologyChatErrorResponse.cs new file mode 100644 index 0000000..46afb1e --- /dev/null +++ b/SynoAI/Notifiers/SynologyChat/SynologyChatErrorResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace SynoAI.Notifiers.SynologyChat +{ + public class SynologyChatErrorResponse + { + [JsonProperty("code")] + public string Code { get; set; } + [JsonProperty("errors")] + public SynologyChatErrorReasonResponse Errors { get; set; } + } +} diff --git a/SynoAI/Notifiers/SynologyChat/SynologyChatFactory.cs b/SynoAI/Notifiers/SynologyChat/SynologyChatFactory.cs new file mode 100644 index 0000000..65ca11f --- /dev/null +++ b/SynoAI/Notifiers/SynologyChat/SynologyChatFactory.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace SynoAI.Notifiers.SynologyChat +{ + public class SynologyChatFactory : NotifierFactory + { + public override INotifier Create(ILogger logger, IConfigurationSection section) + { + using (logger.BeginScope(nameof(SynologyChatFactory))) + { + logger.LogInformation($"Processing {nameof(SynologyChat)} Config"); + + string url = section.GetValue("Url"); + + SynologyChat webhook = new SynologyChat() + { + Url = url + }; + + return webhook; + } + } + } +} diff --git a/SynoAI/Notifiers/SynologyChat/SynologyChatResponse.cs b/SynoAI/Notifiers/SynologyChat/SynologyChatResponse.cs new file mode 100644 index 0000000..db6e00d --- /dev/null +++ b/SynoAI/Notifiers/SynologyChat/SynologyChatResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace SynoAI.Notifiers.SynologyChat +{ + public class SynologyChatResponse + { + [JsonProperty("success")] + public bool Success { get; set; } + [JsonProperty("error")] + public SynologyChatErrorResponse Error { get; set; } + } +}