From bbf94d215ff84904876287f19a45b97c40f712c4 Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 14 May 2025 23:44:03 -0400 Subject: [PATCH] webhook functions --- Behavior/TwitchSummon.cs | 1 - Behavior/Webhook.cs | 181 +++++++++++++++++++++++++++++++++++++++ ConsoleService.cs | 3 +- Models/Enums.cs | 16 +++- Program.cs | 2 +- appsettings.json | 9 +- 6 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 Behavior/Webhook.cs diff --git a/Behavior/TwitchSummon.cs b/Behavior/TwitchSummon.cs index ec0882b..ea5a0da 100644 --- a/Behavior/TwitchSummon.cs +++ b/Behavior/TwitchSummon.cs @@ -24,7 +24,6 @@ public class TwitchSummon : Behavior OwnerId = uacID, DisplayName = Name }; - Rememberer.RememberUAC(myUAC); } } public override bool ShouldAct(Message message) diff --git a/Behavior/Webhook.cs b/Behavior/Webhook.cs new file mode 100644 index 0000000..9567d97 --- /dev/null +++ b/Behavior/Webhook.cs @@ -0,0 +1,181 @@ +namespace vassago.Behavior; + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using vassago.Models; + +[StaticPlz] +public class Webhook : Behavior +{ + public override string Name => "Webhook"; + + public override string Trigger => "!hook"; + + public override string Description => "call a webhook"; + + private static List configuredWebhooks = new List(); + private ConcurrentDictionary authedCache = new ConcurrentDictionary(); + private HttpClient hc = new HttpClient(); + + public static void SetupWebhooks(IConfigurationSection confSection) + { + configuredWebhooks = confSection.Get>(); + + foreach (var conf in configuredWebhooks) + { + var confName = $"Webhook: {conf.Trigger}"; + Console.WriteLine($"confName: {confName}; conf.uri: {conf.Uri}, conf.uacID: {conf.uacID}, conf.Method: {conf.Method}, conf.Headers: {conf.Headers?.Count() ?? 0}, conf.Content: {conf.Content}"); + foreach (var kvp in conf.Headers) + { + Console.WriteLine($"{kvp[0]}: {kvp[1]}"); + } + var myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == conf.uacID); + if (myUAC == null) + { + myUAC = new() + { + OwnerId = conf.uacID, + DisplayName = confName + }; + Rememberer.RememberUAC(myUAC); + } + else + { + if (myUAC.DisplayName != confName) + { + myUAC.DisplayName = confName; + Rememberer.RememberUAC(myUAC); + } + } + } + } + + public override bool ShouldAct(Message message) + { + if (!base.ShouldAct(message)) + return false; + + Console.WriteLine("webhook checking"); + + if (configuredWebhooks?.Count() < 1) + { + Console.Error.WriteLine("no webhooks configured!"); + } + + var webhookableMessageContent = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1); + Console.WriteLine($"webhookable content: {webhookableMessageContent}"); + foreach (var wh in configuredWebhooks) + { + if (webhookableMessageContent.StartsWith(wh.Trigger)) + { + var uacConf = Rememberer.SearchUAC(uac => uac.OwnerId == wh.uacID); + if (uacConf.Users.Contains(message.Author.IsUser) || uacConf.Channels.Contains(message.Channel) || uacConf.AccountInChannels.Contains(message.Author)) + { + Console.WriteLine("webhook UAC passed, preparing WebhookActionOrder"); + authedCache.TryAdd(message.Id, new WebhookActionOrder() + { + Conf = wh, + webhookContent = webhookableMessageContent.Substring(wh.Trigger.Length + 1), + }); + Console.WriteLine($"added {message.Id} to authedcache"); + return true; + } + } + } + return false; + } + public override async Task ActOn(Message message) + { + Console.WriteLine($"hi i'm ActOn. acting on {message.Id}"); + WebhookActionOrder actionOrder; + if (!authedCache.TryRemove(message.Id, out actionOrder)) + { + Console.Error.WriteLine($"{message.Id} was supposed to act, but authedCache doesn't have it! it has {authedCache?.Count()} other stuff, though."); + return false; + } + var req = new HttpRequestMessage(new HttpMethod(actionOrder.Conf.Method.ToString()), actionOrder.Conf.Uri); + var theContentHeader = actionOrder.Conf.Headers?.FirstOrDefault(h => h[0]?.ToLower() == "content-type"); + if (theContentHeader != null) + { + switch (theContentHeader[1]?.ToLower()) + { + //json content is constructed some other weird way. + case "multipart/form-data": + req.Content = new System.Net.Http.MultipartFormDataContent(translate(actionOrder)); + break; + default: + req.Content = new System.Net.Http.StringContent(translate(actionOrder)); + break; + } + req.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(theContentHeader[1]?.ToLower()); + } + if (req.Content == null) + { + req.Content = new System.Net.Http.StringContent(translate(actionOrder)); + } + Console.WriteLine($"survived translating string content. request content: {req.Content}"); + if (actionOrder.Conf.Headers?.ToList().Count > 0) + { + Console.WriteLine("will add headers."); + foreach (var kvp in actionOrder.Conf.Headers.ToList()) + { + if (kvp[0] == theContentHeader[0]) + { + Console.WriteLine("content header; skipping."); + } + else + { + Console.WriteLine($"adding header; {kvp[0]}: {kvp[1]}"); + req.Headers.Add(kvp[0], kvp[1]); + Console.WriteLine("survived."); + } + } + } + else + { + Console.WriteLine("no headers to add."); + } + Console.WriteLine("about to Send."); + var response = hc.Send(req); + Console.WriteLine($"{response.StatusCode} - {response.ReasonPhrase}"); + if (!response.IsSuccessStatusCode) + { + var tragedy = $"{response.StatusCode} - {response.ReasonPhrase} - {await response.Content.ReadAsStringAsync()}"; + Console.Error.WriteLine(tragedy); + await message.Reply(tragedy); + } + else + { + await message.Reply(await response.Content.ReadAsStringAsync()); + } + return true; + } + private string translate(WebhookActionOrder actionOrder) + { + //TODO: message author, etc. + return actionOrder.Conf.Content?.Replace("{text}", actionOrder.webhookContent); + } +} + +public class WebhookConf +{ + public Guid uacID { get; set; } + public string Trigger { get; set; } + public Uri Uri { get; set; } + //public HttpMethod Method { get; set; } + public Enumerations.HttpVerb Method { get; set; } + public List> Headers { get; set; } + public string Content { get; set; } +} +public class WebhookActionOrder +{ + public WebhookConf Conf { get; set; } + public string webhookContent { get; set; } +} diff --git a/ConsoleService.cs b/ConsoleService.cs index 2dc42bc..a1694fd 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -16,6 +16,7 @@ namespace vassago DiscordTokens = aspConfig.GetSection("DiscordTokens").Get>(); TwitchConfigs = aspConfig.GetSection("TwitchConfigs").Get>(); Conversion.Converter.Load(aspConfig["ExchangePairsLocation"]); + vassago.Behavior.Webhook.SetupWebhooks(aspConfig.GetSection("Webhooks")); } IEnumerable DiscordTokens { get; } @@ -51,4 +52,4 @@ namespace vassago return null; } } -} \ No newline at end of file +} diff --git a/Models/Enums.cs b/Models/Enums.cs index 9cc016d..2531719 100644 --- a/Models/Enums.cs +++ b/Models/Enums.cs @@ -38,6 +38,20 @@ public static class Enumerations [Description("organizational psuedo-channel")] OU } + /// + ///bro. don't even get me started. tl;dr: hashtag microsoft. + /// + public enum HttpVerb + { + Get, + Post, + Put, + Delete, + Head, + Patch, + Options + } + public static string GetDescription(this T enumerationValue) where T : struct @@ -64,4 +78,4 @@ public static class Enumerations //If we have no description attribute, just return the ToString of the enum return enumerationValue.ToString(); } -} \ No newline at end of file +} diff --git a/Program.cs b/Program.cs index 6e8287e..c932e84 100644 --- a/Program.cs +++ b/Program.cs @@ -45,7 +45,7 @@ app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "api"); }); -app.UseExceptionHandler(); +//app.UseExceptionHandler(); app.UseStatusCodePages(); if (app.Environment.IsDevelopment()) diff --git a/appsettings.json b/appsettings.json index 51f6b4d..bab73e9 100644 --- a/appsettings.json +++ b/appsettings.json @@ -14,5 +14,12 @@ ], "exchangePairsLocation": "assets/exchangepairs.json", "DBConnectionString": "Host=azure.club;Database=db;Username=user;Password=password", - "SetupSlashCommands": false + "SetupSlashCommands": false, + "Webhooks": [ + { + "uacID": "9a94855a-e5a2-43b5-8420-ce670472ce95", + "Trigger": "test", + "Uri": "http://localhost" + } + ] }