diff --git a/API/Schema/NotificationConnectors/Gotify.cs b/API/Schema/NotificationConnectors/Gotify.cs index bc43d29..ca8bb6c 100644 --- a/API/Schema/NotificationConnectors/Gotify.cs +++ b/API/Schema/NotificationConnectors/Gotify.cs @@ -1,8 +1,42 @@ -namespace API.Schema.NotificationConnectors; +using System.Text; +using Newtonsoft.Json; + +namespace API.Schema.NotificationConnectors; public class Gotify(string endpoint, string appToken) : NotificationConnector(TokenGen.CreateToken(typeof(Gotify), 64), NotificationConnectorType.Gotify) { public string Endpoint { get; init; } = endpoint; public string AppToken { get; init; } = appToken; + + protected override void SendNotificationInternal(string title, string notificationText) + { + MessageData message = new(title, notificationText); + HttpRequestMessage request = new(HttpMethod.Post, $"{endpoint}/message"); + request.Headers.Add("X-Gotify-Key", this.AppToken); + request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); + HttpResponseMessage response = _client.Send(request); + if (!response.IsSuccessStatusCode) + { + StreamReader sr = new (response.Content.ReadAsStream()); + //TODO + } + } + + private class MessageData + { + // ReSharper disable four times UnusedAutoPropertyAccessor.Local + public string message { get; } + public long priority { get; } + public string title { get; } + public Dictionary extras { get; } + + public MessageData(string title, string message) + { + this.title = title; + this.message = message; + this.extras = new(); + this.priority = 4; + } + } } \ No newline at end of file diff --git a/API/Schema/NotificationConnectors/Lunasea.cs b/API/Schema/NotificationConnectors/Lunasea.cs index db6bd6b..7463de5 100644 --- a/API/Schema/NotificationConnectors/Lunasea.cs +++ b/API/Schema/NotificationConnectors/Lunasea.cs @@ -1,7 +1,35 @@ -namespace API.Schema.NotificationConnectors; +using System.Text; +using Newtonsoft.Json; + +namespace API.Schema.NotificationConnectors; public class Lunasea(string id) : NotificationConnector(TokenGen.CreateToken(typeof(Lunasea), 64), NotificationConnectorType.LunaSea) { public string Id { get; init; } = id; + protected override void SendNotificationInternal(string title, string notificationText) + { + MessageData message = new(title, notificationText); + HttpRequestMessage request = new(HttpMethod.Post, $"https://notify.lunasea.app/v1/custom/{id}"); + request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); + HttpResponseMessage response = _client.Send(request); + if (!response.IsSuccessStatusCode) + { + StreamReader sr = new (response.Content.ReadAsStream()); + //TODO + } + } + + private class MessageData + { + // ReSharper disable twice UnusedAutoPropertyAccessor.Local + public string title { get; } + public string body { get; } + + public MessageData(string title, string body) + { + this.title = title; + this.body = body; + } + } } \ No newline at end of file diff --git a/API/Schema/NotificationConnectors/NotificationConnector.cs b/API/Schema/NotificationConnectors/NotificationConnector.cs index 64c53be..909d219 100644 --- a/API/Schema/NotificationConnectors/NotificationConnector.cs +++ b/API/Schema/NotificationConnectors/NotificationConnector.cs @@ -1,5 +1,7 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; namespace API.Schema.NotificationConnectors; @@ -8,6 +10,11 @@ public abstract class NotificationConnector(string notificationConnectorId, Noti { [MaxLength(64)] public string NotificationConnectorId { get; } = notificationConnectorId; - public NotificationConnectorType NotificationConnectorType { get; init; } = notificationConnectorType; + + [JsonIgnore] + [NotMapped] + protected readonly HttpClient _client = new(); + + protected abstract void SendNotificationInternal(string title, string notificationText); } \ No newline at end of file diff --git a/API/Schema/NotificationConnectors/Ntfy.cs b/API/Schema/NotificationConnectors/Ntfy.cs index 446c261..318b502 100644 --- a/API/Schema/NotificationConnectors/Ntfy.cs +++ b/API/Schema/NotificationConnectors/Ntfy.cs @@ -1,9 +1,77 @@ -namespace API.Schema.NotificationConnectors; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; -public class Ntfy(string endpoint, string auth, string topic) - : NotificationConnector(TokenGen.CreateToken(typeof(Ntfy), 64), NotificationConnectorType.Ntfy) +namespace API.Schema.NotificationConnectors; + +public class Ntfy : NotificationConnector { - public string Endpoint { get; init; } = endpoint; - public string Auth { get; init; } = auth; - public string Topic { get; init; } = topic; + private NotificationConnector _notificationConnectorImplementation; + public string Endpoint { get; init; } + public string Auth { get; init; } + public string Topic { get; init; } + + public Ntfy(string endpoint, string auth, string topic): base(TokenGen.CreateToken(typeof(Ntfy), 64), NotificationConnectorType.Ntfy) + { + Endpoint = endpoint; + Auth = auth; + Topic = topic; + } + + public Ntfy(string endpoint, string username, string password, string? topic = null) : + this(EndpointAndTopicFromUrl(endpoint)[0], topic??EndpointAndTopicFromUrl(endpoint)[1], AuthFromUsernamePassword(username, password)) + { + + } + + private static string AuthFromUsernamePassword(string username, string password) + { + string authHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); + string authParam = Convert.ToBase64String(Encoding.UTF8.GetBytes(authHeader)).Replace("=",""); + return authParam; + } + + private static string[] EndpointAndTopicFromUrl(string url) + { + string[] ret = new string[2]; + Regex rootUriRex = new(@"(https?:\/\/[a-zA-Z0-9-\.]+\.[a-zA-Z0-9]+)(?:\/([a-zA-Z0-9-\.]+))?.*"); + Match match = rootUriRex.Match(url); + if(!match.Success) + throw new ArgumentException($"Error getting URI from provided endpoint-URI: {url}"); + + ret[0] = match.Groups[1].Value; + ret[1] = match.Groups[2].Success && match.Groups[2].Value.Length > 0 ? match.Groups[2].Value : "tranga"; + + return ret; + } + + protected override void SendNotificationInternal(string title, string notificationText) + { + MessageData message = new(title, Topic, notificationText); + HttpRequestMessage request = new(HttpMethod.Post, $"{this.Endpoint}?auth={this.Auth}"); + request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); + HttpResponseMessage response = _client.Send(request); + if (!response.IsSuccessStatusCode) + { + StreamReader sr = new (response.Content.ReadAsStream()); + //TODO + } + } + + private class MessageData + { + // ReSharper disable UnusedAutoPropertyAccessor.Local + public string topic { get; } + public string title { get; } + public string message { get; } + public int priority { get; } + + public MessageData(string title, string topic, string message) + { + this.topic = topic; + this.title = title; + this.message = message; + this.priority = 3; + } + } } \ No newline at end of file diff --git a/Tranga/NotificationConnectors/Gotify.cs b/Tranga/NotificationConnectors/Gotify.cs deleted file mode 100644 index 3dc7f28..0000000 --- a/Tranga/NotificationConnectors/Gotify.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Text; -using Newtonsoft.Json; - -namespace Tranga.NotificationConnectors; - -public class Gotify : NotificationConnector -{ - public string endpoint { get; } - // ReSharper disable once MemberCanBePrivate.Global - public string appToken { get; } - private readonly HttpClient _client = new(); - - [JsonConstructor] - public Gotify(GlobalBase clone, string endpoint, string appToken) : base(clone, NotificationConnectorType.Gotify) - { - if (!baseUrlRex.IsMatch(endpoint)) - throw new ArgumentException("endpoint does not match pattern"); - this.endpoint = baseUrlRex.Match(endpoint).Value;; - this.appToken = appToken; - } - - public override string ToString() - { - return $"Gotify {endpoint}"; - } - - protected override void SendNotificationInternal(string title, string notificationText) - { - Log($"Sending notification: {title} - {notificationText}"); - MessageData message = new(title, notificationText); - HttpRequestMessage request = new(HttpMethod.Post, $"{endpoint}/message"); - request.Headers.Add("X-Gotify-Key", this.appToken); - request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); - HttpResponseMessage response = _client.Send(request); - if (!response.IsSuccessStatusCode) - { - StreamReader sr = new (response.Content.ReadAsStream()); - Log($"{response.StatusCode}: {sr.ReadToEnd()}"); - } - } - - private class MessageData - { - // ReSharper disable four times UnusedAutoPropertyAccessor.Local - public string message { get; } - public long priority { get; } - public string title { get; } - public Dictionary extras { get; } - - public MessageData(string title, string message) - { - this.title = title; - this.message = message; - this.extras = new(); - this.priority = 4; - } - } -} \ No newline at end of file diff --git a/Tranga/NotificationConnectors/LunaSea.cs b/Tranga/NotificationConnectors/LunaSea.cs deleted file mode 100644 index e124192..0000000 --- a/Tranga/NotificationConnectors/LunaSea.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text; -using Newtonsoft.Json; - -namespace Tranga.NotificationConnectors; - -public class LunaSea : NotificationConnector -{ - // ReSharper disable once MemberCanBePrivate.Global - public string id { get; init; } - private readonly HttpClient _client = new(); - - [JsonConstructor] - public LunaSea(GlobalBase clone, string id) : base(clone, NotificationConnectorType.LunaSea) - { - this.id = id; - } - - public override string ToString() - { - return $"LunaSea {id}"; - } - - protected override void SendNotificationInternal(string title, string notificationText) - { - Log($"Sending notification: {title} - {notificationText}"); - MessageData message = new(title, notificationText); - HttpRequestMessage request = new(HttpMethod.Post, $"https://notify.lunasea.app/v1/custom/{id}"); - request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); - HttpResponseMessage response = _client.Send(request); - if (!response.IsSuccessStatusCode) - { - StreamReader sr = new (response.Content.ReadAsStream()); - Log($"{response.StatusCode}: {sr.ReadToEnd()}"); - } - } - - private class MessageData - { - // ReSharper disable twice UnusedAutoPropertyAccessor.Local - public string title { get; } - public string body { get; } - - public MessageData(string title, string body) - { - this.title = title; - this.body = body; - } - } -} \ No newline at end of file diff --git a/Tranga/NotificationConnectors/NotificationConnector.cs b/Tranga/NotificationConnectors/NotificationConnector.cs deleted file mode 100644 index f64da82..0000000 --- a/Tranga/NotificationConnectors/NotificationConnector.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace Tranga.NotificationConnectors; - -public abstract class NotificationConnector : GlobalBase -{ - public readonly NotificationConnectorType notificationConnectorType; - private DateTime? _notificationRequested = null; - private readonly Thread? _notificationBufferThread = null; - private const int NoChangeTimeout = 3, BiggestInterval = 30; - private List> _notifications = new(); - - protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone) - { - Log($"Creating notificationConnector {Enum.GetName(notificationConnectorType)}"); - this.notificationConnectorType = notificationConnectorType; - - - if (TrangaSettings.bufferLibraryUpdates) - { - _notificationBufferThread = new(CheckNotificationBuffer); - _notificationBufferThread.Start(); - } - } - - private void CheckNotificationBuffer() - { - while (true) - { - if (_notificationRequested is not null && DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library - { - string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray(); - Log($"Notification Buffer sending! Notifications: {string.Join(", ", uniqueTitles)}"); - foreach (string ut in uniqueTitles) - { - string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); - SendNotificationInternal($"{ut} ({texts.Length})", string.Join('\n', texts)); - } - _notificationRequested = null; - _notifications.Clear(); - } - Thread.Sleep(100); - } - } - - public enum NotificationConnectorType : byte { Gotify = 0, LunaSea = 1, Ntfy = 2 } - - public void SendNotification(string title, string notificationText, bool buffer = false) - { - _notificationRequested ??= DateTime.Now; - if (!TrangaSettings.bufferNotifications || !buffer) - { - SendNotificationInternal(title, notificationText); - return; - } - _notifications.Add(new(title, notificationText)); - if (_notificationRequested is not null && - DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library - { - string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray(); - foreach (string ut in uniqueTitles) - { - string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray(); - SendNotificationInternal(ut, string.Join('\n', texts)); - } - _notificationRequested = null; - _notifications.Clear(); - } - else if(_notificationRequested is not null) - { - Log($"Buffering Notifications (Updates in latest {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})"); - } - } - - protected abstract void SendNotificationInternal(string title, string notificationText); -} \ No newline at end of file diff --git a/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs b/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs deleted file mode 100644 index 6f310e1..0000000 --- a/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Tranga.NotificationConnectors; - -public class NotificationManagerJsonConverter : JsonConverter -{ - private GlobalBase _clone; - - public NotificationManagerJsonConverter(GlobalBase clone) - { - this._clone = clone; - } - - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(NotificationConnector)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, - JsonSerializer serializer) - { - JObject jo = JObject.Load(reader); - switch (jo["notificationConnectorType"]!.Value()) - { - case (byte)NotificationConnector.NotificationConnectorType.Gotify: - return new Gotify(this._clone, jo.GetValue("endpoint")!.Value()!, jo.GetValue("appToken")!.Value()!); - case (byte)NotificationConnector.NotificationConnectorType.LunaSea: - return new LunaSea(this._clone, jo.GetValue("id")!.Value()!); - case (byte)NotificationConnector.NotificationConnectorType.Ntfy: - return new Ntfy(this._clone, jo.GetValue("endpoint")!.Value()!, jo.GetValue("topic")!.Value()!, jo.GetValue("auth")!.Value()!); - } - - throw new Exception(); - } - - public override bool CanWrite => false; - - /// - /// Don't call this - /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - throw new Exception("Dont call this"); - } -} \ No newline at end of file diff --git a/Tranga/NotificationConnectors/Ntfy.cs b/Tranga/NotificationConnectors/Ntfy.cs deleted file mode 100644 index afad9a6..0000000 --- a/Tranga/NotificationConnectors/Ntfy.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -namespace Tranga.NotificationConnectors; - -public class Ntfy : NotificationConnector -{ - // ReSharper disable twice MemberCanBePrivate.Global - public string endpoint { get; init; } - public string auth { get; init; } - public string topic { get; init; } - private readonly HttpClient _client = new(); - - [JsonConstructor] - public Ntfy(GlobalBase clone, string endpoint, string topic, string auth) : base(clone, NotificationConnectorType.Ntfy) - { - this.endpoint = endpoint; - this.topic = topic; - this.auth = auth; - } - - public Ntfy(GlobalBase clone, string endpoint, string username, string password, string? topic = null) : - this(clone, EndpointAndTopicFromUrl(endpoint)[0], topic??EndpointAndTopicFromUrl(endpoint)[1], AuthFromUsernamePassword(username, password)) - { - - } - - private static string AuthFromUsernamePassword(string username, string password) - { - string authHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); - string authParam = Convert.ToBase64String(Encoding.UTF8.GetBytes(authHeader)).Replace("=",""); - return authParam; - } - - private static string[] EndpointAndTopicFromUrl(string url) - { - string[] ret = new string[2]; - if (!baseUrlRex.IsMatch(url)) - throw new ArgumentException("url does not match pattern"); - Regex rootUriRex = new(@"(https?:\/\/[a-zA-Z0-9-\.]+\.[a-zA-Z0-9]+)(?:\/([a-zA-Z0-9-\.]+))?.*"); - Match match = rootUriRex.Match(url); - if(!match.Success) - throw new ArgumentException($"Error getting URI from provided endpoint-URI: {url}"); - - ret[0] = match.Groups[1].Value; - ret[1] = match.Groups[2].Success && match.Groups[2].Value.Length > 0 ? match.Groups[2].Value : "tranga"; - - return ret; - } - - public override string ToString() - { - return $"Ntfy {endpoint} {topic}"; - } - - protected override void SendNotificationInternal(string title, string notificationText) - { - Log($"Sending notification: {title} - {notificationText}"); - MessageData message = new(title, topic, notificationText); - HttpRequestMessage request = new(HttpMethod.Post, $"{this.endpoint}?auth={this.auth}"); - request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); - HttpResponseMessage response = _client.Send(request); - if (!response.IsSuccessStatusCode) - { - StreamReader sr = new (response.Content.ReadAsStream()); - Log($"{response.StatusCode}: {sr.ReadToEnd()}"); - } - } - - private class MessageData - { - // ReSharper disable UnusedAutoPropertyAccessor.Local - public string topic { get; } - public string title { get; } - public string message { get; } - public int priority { get; } - - public MessageData(string title, string topic, string message) - { - this.topic = topic; - this.title = title; - this.message = message; - this.priority = 3; - } - } -} \ No newline at end of file