From 7c7793f3b2c7828926feb3020e8d140f2f01da4d Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 29 May 2025 13:09:57 -0400 Subject: [PATCH] send message works! ...messages aren't getting stored, though. --- Behaver.cs | 70 ++++++++- Behavior/ChatGPTSnark.cs | 4 +- Behavior/DefinitionSnarkCogDiss.cs | 4 +- Behavior/DefinitionSnarkGaslight.cs | 4 +- Behavior/Detiktokify.cs | 27 ++-- Behavior/FiximageHeic.cs | 8 +- Behavior/GeneralSnarkCloudNative.cs | 16 +- Behavior/GeneralSnarkGooglit.cs | 8 +- Behavior/GeneralSnarkMisspellDefinitely.cs | 4 +- Behavior/GeneralSnarkPlaying.cs | 4 +- Behavior/GeneralSnarkSkynet.cs | 8 +- Behavior/Gratitude.cs | 26 +-- Behavior/Joke.cs | 16 +- Behavior/LinkMe.cs | 11 +- Behavior/Peptalk.cs | 100 ------------ Behavior/PulseCheck.cs | 6 +- Behavior/QRify.cs | 12 +- Behavior/RoomRead.cs | 2 +- Behavior/TwitchSummon.cs | 83 +++++----- Behavior/UnitConvert.cs | 6 +- Behavior/Webhook.cs | 4 +- Behavior/WishLuck.cs | 6 +- ConsoleService.cs | 4 +- Models/Channel.cs | 7 - Models/Message.cs | 7 - Program.cs | 2 - .../DiscordInterface/DiscordInterface.cs | 148 +++++++++++++----- ProtocolInterfaces/ProtocolInterface.cs | 13 ++ ProtocolInterfaces/ProtocolList.cs | 7 - .../TwitchInterface/TwitchInterface.cs | 81 +++++----- Rememberer.cs | 4 +- Shared.cs | 3 +- .../Controllers/ChannelsController.cs | 8 +- .../api/InternalAPIProtocolController.cs | 45 ++++++ .../Controllers/api/RemembererController.cs | 16 +- WebInterface/Views/Channels/Details.cshtml | 10 +- WebInterface/Views/Users/Details.cshtml | 6 +- vassago.csproj | 6 +- wwwroot/js/site.js | 17 +- 39 files changed, 441 insertions(+), 372 deletions(-) delete mode 100644 Behavior/Peptalk.cs create mode 100644 ProtocolInterfaces/ProtocolInterface.cs delete mode 100644 ProtocolInterfaces/ProtocolList.cs create mode 100644 WebInterface/Controllers/api/InternalAPIProtocolController.cs diff --git a/Behaver.cs b/Behaver.cs index f93acf5..df89777 100644 --- a/Behaver.cs +++ b/Behaver.cs @@ -1,9 +1,9 @@ namespace vassago; -#pragma warning disable 4014 using gray_messages.chat; using franz; using vassago.Behavior; using vassago.Models; +using vassago.ProtocolInterfaces; using System; using System.Linq; using System.Text; @@ -49,7 +49,7 @@ public class Behaver foreach (var behavior in Behaviors.ToList()) { //if (!behavior.ShouldAct(message, matchingUACs)) //TODO: this way - if(!behavior.ShouldAct(message)) + if (!behavior.ShouldAct(message)) { continue; } @@ -65,7 +65,7 @@ public class Behaver @"Well, that's a great question, and there are certainly many different possible answers. Ultimately, the decision will depend on a variety of factors, including your personal interests and goals, as well as any practical considerations (like the economy). I encourage you to do your research, speak with experts and educators, and explore your options before making a decision that's right for you.", @"┐(゚ ~゚ )┌", @"¯\_(ツ)_/¯", @"╮ (. ❛ ᴗ ❛.) ╭", @"╮(╯ _╰ )╭" }; - await message.Channel.SendMessage(responses[Shared.r.Next(responses.Count)]); + Behaver.Instance.SendMessage(message.Channel.Id, responses[Shared.r.Next(responses.Count)]); message.ActedOn = true; behaviorsActedOn.Add("generic question fallback"); } @@ -148,5 +148,67 @@ public class Behaver Rememberer.RememberUser(primary); return true; } + private ProtocolInterface fetchInterface(Channel ch) + { + var walkUp = ch; + while (walkUp.ParentChannel != null) + { + walkUp = walkUp.ParentChannel; + } + foreach (var iproto in Shared.ProtocolList) + { + if (iproto.SelfChannel.Id == walkUp.Id) + return iproto; + } + return null; + } + public async Task SendMessage(Guid channelId, string text) + { + var channel = Rememberer.ChannelDetail(channelId); + if (channel == null) + return 404; + var iprotocol = fetchInterface(channel); + if (iprotocol == null) + return 404; + + return await iprotocol.SendMessage(channel, text); + } + public async Task React(Guid messageId, string reaction) + { + var message = Rememberer.MessageDetail(messageId); + if (message == null) + return 404; + var iprotocol = fetchInterface(message.Channel); + if (iprotocol == null) + return 404; + + return await iprotocol.React(message, reaction); + } + public async Task Reply(Guid messageId, string text) + { + var message = Rememberer.MessageDetail(messageId); + if (message == null) + { + Console.WriteLine($"message {messageId} not found"); + return 404; + } + var iprotocol = fetchInterface(message.Channel); + if (iprotocol == null) + { + Console.WriteLine($"couldn't find channel for {message.Channel.Id} not found"); + return 404; + } + return await iprotocol.Reply(message, text); + } + public async Task SendFile(Guid channelId, string path, string accompanyingText) + { + var channel = Rememberer.ChannelDetail(channelId); + if (channel == null) + return 404; + var iprotocol = fetchInterface(channel); + if (iprotocol == null) + return 404; + + return await iprotocol.SendFile(channel, path, accompanyingText); + } } -#pragma warning restore 4014 //the "async not awaited" error diff --git a/Behavior/ChatGPTSnark.cs b/Behavior/ChatGPTSnark.cs index 7df5292..055bd69 100644 --- a/Behavior/ChatGPTSnark.cs +++ b/Behavior/ChatGPTSnark.cs @@ -19,7 +19,7 @@ public class ChatGPTSnark : Behavior public override async Task ActOn(Message message) { - await message.Channel.SendMessage("chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?"); + Behaver.Instance.SendMessage(message.Channel.Id, "chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?"); return true; } -} \ No newline at end of file +} diff --git a/Behavior/DefinitionSnarkCogDiss.cs b/Behavior/DefinitionSnarkCogDiss.cs index 9c88939..551c78d 100644 --- a/Behavior/DefinitionSnarkCogDiss.cs +++ b/Behavior/DefinitionSnarkCogDiss.cs @@ -28,7 +28,7 @@ public class DefinitionSnarkCogDiss : Behavior public override async Task ActOn(Message message) { - await message.Reply("that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?"); + Behaver.Instance.SendMessage(message.Channel.Id, "that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?"); return true; } -} \ No newline at end of file +} diff --git a/Behavior/DefinitionSnarkGaslight.cs b/Behavior/DefinitionSnarkGaslight.cs index c6abb90..00d7333 100644 --- a/Behavior/DefinitionSnarkGaslight.cs +++ b/Behavior/DefinitionSnarkGaslight.cs @@ -28,7 +28,7 @@ public class DefinitionSnarkGaslight : Behavior public override async Task ActOn(Message message) { - await message.Channel.SendMessage("that's not what gaslight means. Did you mean \"deceive\"?"); + Behaver.Instance.SendMessage(message.Channel.Id, "that's not what gaslight means. Did you mean \"deceive\"?"); return true; } -} \ No newline at end of file +} diff --git a/Behavior/Detiktokify.cs b/Behavior/Detiktokify.cs index fa4f553..c113e0a 100644 --- a/Behavior/Detiktokify.cs +++ b/Behavior/Detiktokify.cs @@ -27,10 +27,10 @@ public class Detiktokify : Behavior public override bool ShouldAct(Message message) { - if(Behaver.Instance.IsSelf(message.Author.Id)) + if (Behaver.Instance.IsSelf(message.Author.Id)) return false; - if(message.Channel.EffectivePermissions.MaxAttachmentBytes == 0) + if (message.Channel.EffectivePermissions.MaxAttachmentBytes == 0) return false; var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries); @@ -45,29 +45,27 @@ public class Detiktokify : Behavior } } } - if(tiktokLinks.Any()){ + if (tiktokLinks.Any()) + { Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}"); } return tiktokLinks.Any(); } public override async Task ActOn(Message message) { - foreach(var link in tiktokLinks) + foreach (var link in tiktokLinks) { tiktokLinks.Remove(link); try { Console.WriteLine($"detiktokifying {link}"); - #pragma warning disable 4014 - //await message.React("<:tiktok:1070038619584200884>"); - #pragma warning restore 4014 - var res = await ytdl.RunVideoDownload(link.ToString()); if (!res.Success) { Console.Error.WriteLine("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput)); - await message.React("problemon"); - await message.Channel.SendMessage("tried to dl, failed. \n"); + + Behaver.Instance.SendMessage(message.Channel.Id, "tried to dl, failed. \n"); + Behaver.Instance.React(message.Channel.Id, "problemon"); } else { @@ -79,12 +77,12 @@ public class Detiktokify : Behavior { try { - await message.Channel.SendFile(path, null); + Behaver.Instance.SendFile(message.Channel.Id, path, null); } catch (Exception e) { System.Console.Error.WriteLine(e); - await message.Channel.SendMessage($"aaaadam!\n{e}"); + Behaver.Instance.SendMessage(message.Channel.Id, $"aaaadam!\n{e}"); } } else @@ -97,14 +95,15 @@ public class Detiktokify : Behavior else { Console.Error.WriteLine("idgi but something happened."); - await message.React("problemon"); + + Behaver.Instance.React(message.Id, "problemon"); } } } catch (Exception e) { Console.Error.WriteLine(e); - await message.React("problemon"); + Behaver.Instance.React(message.Id, "problemon"); return false; } } diff --git a/Behavior/FiximageHeic.cs b/Behavior/FiximageHeic.cs index 26ac7af..8ce20b6 100644 --- a/Behavior/FiximageHeic.cs +++ b/Behavior/FiximageHeic.cs @@ -49,7 +49,7 @@ public class FiximageHeic : Behavior conversions.Add(actualDeheic(att, message)); } Task.WaitAll(conversions.ToArray()); - await message.React("\U0001F34F"); + Behaver.Instance.React(message.Id, "\U0001F34F"); return true; } @@ -66,19 +66,19 @@ public class FiximageHeic : Behavior } if (ExternalProcess.GoPlz("convert", $"tmp/{att.Filename} tmp/{att.Filename}.jpg")) { - await message.Channel.SendFile($"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg"); + Behaver.Instance.SendFile(message.Channel.Id, $"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg"); File.Delete($"tmp/{att.Filename}"); File.Delete($"tmp/{att.Filename}.jpg"); } else { - await message.Channel.SendMessage("convert failed :("); + Behaver.Instance.SendMessage(message.Channel.Id, "convert failed :("); Console.Error.WriteLine("convert failed :("); } } catch (Exception e) { - await message.Channel.SendMessage($"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}"); + Behaver.Instance.SendMessage(message.Channel.Id, $"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}"); Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented)); return false; } diff --git a/Behavior/GeneralSnarkCloudNative.cs b/Behavior/GeneralSnarkCloudNative.cs index 90db765..45946ba 100644 --- a/Behavior/GeneralSnarkCloudNative.cs +++ b/Behavior/GeneralSnarkCloudNative.cs @@ -19,13 +19,13 @@ public class GeneralSnarkCloudNative : Behavior public override string Trigger => "certain tech buzzwords that no human uses in normal conversation"; public override bool ShouldAct(Message message) { - if(Behaver.Instance.IsSelf(message.Author.Id)) + if (Behaver.Instance.IsSelf(message.Author.Id)) return false; - if(!message.Channel.EffectivePermissions.ReactionsPossible) + if (!message.Channel.EffectivePermissions.ReactionsPossible) return false; - if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium) + if ((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium) return false; return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) || @@ -37,14 +37,14 @@ public class GeneralSnarkCloudNative : Behavior switch (Shared.r.Next(2)) { case 0: - await message.React("\uD83E\uDD2E"); //vomit emoji + Behaver.Instance.React(message.Id, "\uD83E\uDD2E"); //vomit emoji break; case 1: - await message.React("\uD83C\uDDE7"); //B emoji - await message.React("\uD83C\uDDE6"); //A - await message.React("\uD83C\uDDF3"); //N + Behaver.Instance.React(message.Id, "\uD83C\uDDE7"); //B emoji + Behaver.Instance.React(message.Id, "\uD83C\uDDE6"); //A + Behaver.Instance.React(message.Id, "\uD83C\uDDF3"); //N break; } return true; } -} \ No newline at end of file +} diff --git a/Behavior/GeneralSnarkGooglit.cs b/Behavior/GeneralSnarkGooglit.cs index fd7876b..53bfc90 100644 --- a/Behavior/GeneralSnarkGooglit.cs +++ b/Behavior/GeneralSnarkGooglit.cs @@ -31,7 +31,7 @@ public class GeneralSnarkGooglit : Behavior switch (Shared.r.Next(4)) { default: - await message.Channel.SendMessage("yeah no shit, obviously that resulted in nothing"); + Behaver.Instance.SendMessage(message.Channel.Id, "yeah no shit, obviously that resulted in nothing"); break; case 1: var results = ""; @@ -50,13 +50,13 @@ public class GeneralSnarkGooglit : Behavior results = "the one that had a paragraph that restated the question but badly, a paragraph to give a wrong history on the question, a paragraph with amazon affiliate links, a pargraph that said \"ultimately you should do your own research\", then had a paragraph telling me to give Engagement for The Algorithm"; break; } - await message.Channel.SendMessage("oh here, I memorized the results. My favorite is " + results); + Behaver.Instance.SendMessage(message.Channel.Id, "oh here, I memorized the results. My favorite is " + results); break; case 2: - await message.Channel.SendMessage("Obviously that was already tried. Obviously it failed. If you ever tried to learn anything you'd know that's how it works."); + Behaver.Instance.SendMessage(message.Channel.Id, "Obviously that was already tried. Obviously it failed. If you ever tried to learn anything you'd know that's how it works."); break; case 3: - await message.Channel.SendMessage("\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?"); + Behaver.Instance.SendMessage(message.Channel.Id, "\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?"); break; } return true; diff --git a/Behavior/GeneralSnarkMisspellDefinitely.cs b/Behavior/GeneralSnarkMisspellDefinitely.cs index 0c32d6e..a027093 100644 --- a/Behavior/GeneralSnarkMisspellDefinitely.cs +++ b/Behavior/GeneralSnarkMisspellDefinitely.cs @@ -53,10 +53,10 @@ public class GeneralSnarkMisspellDefinitely : Behavior { if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase)) { - await message.Reply(k + "? so... " + snarkmap[k] + "?"); + Behaver.Instance.Reply(message.Id, k + "? so... " + snarkmap[k] + "?"); return true; } } return true; } -} \ No newline at end of file +} diff --git a/Behavior/GeneralSnarkPlaying.cs b/Behavior/GeneralSnarkPlaying.cs index 89dde0e..bd48ddc 100644 --- a/Behavior/GeneralSnarkPlaying.cs +++ b/Behavior/GeneralSnarkPlaying.cs @@ -32,7 +32,7 @@ public class GeneralSnarkPlaying : Behavior } public override async Task ActOn(Message message) { - await message.Channel.SendMessage("I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED"); + Behaver.Instance.SendMessage(message.Channel.Id, "I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED"); return true; } -} \ No newline at end of file +} diff --git a/Behavior/GeneralSnarkSkynet.cs b/Behavior/GeneralSnarkSkynet.cs index 0d42a93..364de00 100644 --- a/Behavior/GeneralSnarkSkynet.cs +++ b/Behavior/GeneralSnarkSkynet.cs @@ -26,15 +26,15 @@ public class GeneralSnarkSkynet : Behavior switch (Shared.r.Next(5)) { default: - await message.Channel.SendFile("assets/coding and algorithms.png", "i am actually niether a neural-net processor nor a learning computer. but I do use **coding** and **algorithms**."); + Behaver.Instance.SendFile(message.Channel.Id, "assets/coding and algorithms.png", "i am actually niether a neural-net processor nor a learning computer. but I do use **coding** and **algorithms**."); break; case 4: - await message.React("\U0001F644"); //eye roll emoji + Behaver.Instance.React(message.Id, "\U0001F644"); //eye roll emoji break; case 5: - await message.React("\U0001F611"); //emotionless face + Behaver.Instance.React(message.Id, "\U0001F611"); //emotionless face break; } return true; } -} \ No newline at end of file +} diff --git a/Behavior/Gratitude.cs b/Behavior/Gratitude.cs index 5e73af0..2e47f1a 100644 --- a/Behavior/Gratitude.cs +++ b/Behavior/Gratitude.cs @@ -29,47 +29,47 @@ public class Gratitude : Behavior switch (Shared.r.Next(4)) { case 0: - await message.Channel.SendMessage("you're welcome, citizen!"); + await Behaver.Instance.SendMessage(message.Channel.Id, "you're welcome, citizen!"); break; case 1: - await message.React(":)"); + await Behaver.Instance.React(message.Id, ":)"); break; case 2: - await message.React("\U0001F607"); //smiling face with halo + Behaver.Instance.React(message.Id, "\U0001F607"); //smiling face with halo break; case 3: switch (Shared.r.Next(9)) { case 0: - await message.React("<3"); //normal heart, usually rendered red + await Behaver.Instance.React(message.Id, "<3"); //normal heart, usually rendered red break; case 1: - await message.React("\U0001F9E1"); //orange heart + Behaver.Instance.React(message.Id, "\U0001F9E1"); //orange heart break; case 2: - await message.React("\U0001F49B"); //yellow heart + Behaver.Instance.React(message.Id, "\U0001F49B"); //yellow heart break; case 3: - await message.React("\U0001F49A"); //green heart + Behaver.Instance.React(message.Id, "\U0001F49A"); //green heart break; case 4: - await message.React("\U0001F499"); //blue heart + Behaver.Instance.React(message.Id, "\U0001F499"); //blue heart break; case 5: - await message.React("\U0001F49C"); //purple heart + Behaver.Instance.React(message.Id, "\U0001F49C"); //purple heart break; case 6: - await message.React("\U0001F90E"); //brown heart + Behaver.Instance.React(message.Id, "\U0001F90E"); //brown heart break; case 7: - await message.React("\U0001F5A4"); //black heart + Behaver.Instance.React(message.Id, "\U0001F5A4"); //black heart break; case 8: - await message.React("\U0001F90D"); //white heart + Behaver.Instance.React(message.Id, "\U0001F90D"); //white heart break; } break; } return true; } -} \ No newline at end of file +} diff --git a/Behavior/Joke.cs b/Behavior/Joke.cs index e8918d6..d1b921e 100644 --- a/Behavior/Joke.cs +++ b/Behavior/Joke.cs @@ -25,31 +25,29 @@ public class Joke : Behavior jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray(); if (jokes?.Length == 0) { - await message.Channel.SendMessage("I don't know any. Adam!"); + Behaver.Instance.SendMessage(message.Channel.Id, "I don't know any. Adam!"); } var thisJoke = jokes[Shared.r.Next(jokes.Length)]; if (thisJoke.Contains("?") && !thisJoke.EndsWith('?')) { - #pragma warning disable 4014 Task.Run(async () => { var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1; var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark); var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark).Trim(); - Task.WaitAll(message.Channel.SendMessage(straightline)); + Task.WaitAll(Behaver.Instance.SendMessage(message.Channel.Id, straightline)); Thread.Sleep(TimeSpan.FromSeconds(Shared.r.Next(5, 30))); if (message.Channel.EffectivePermissions.ReactionsPossible == true && Shared.r.Next(8) == 0) { Behaver.Behaviors.Add(new LaughAtOwnJoke(punchline)); } - await message.Channel.SendMessage(punchline); + Behaver.Instance.SendMessage(message.Channel.Id, punchline); // var myOwnMsg = await message.Channel.SendMessage(punchline); }); - #pragma warning restore 4014 } else { - await message.Channel.SendMessage(thisJoke); + Behaver.Instance.SendMessage(message.Channel.Id, thisJoke); } return true; } @@ -69,7 +67,7 @@ public class LaughAtOwnJoke : Behavior } public override bool ShouldAct(Message message) { - if(Behaver.Instance.IsSelf(message.Author.Id)) + if (Behaver.Instance.IsSelf(message.Author.Id)) return false; Console.WriteLine($"{message.Content} == {_punchline}"); @@ -79,8 +77,8 @@ public class LaughAtOwnJoke : Behavior public override async Task ActOn(Message message) { - await message.React("\U0001F60E"); //smiling face with sunglasses + Behaver.Instance.React(message.Id, "\U0001F60E"); //smiling face with sunglasses Behaver.Behaviors.Remove(this); return true; } -} \ No newline at end of file +} diff --git a/Behavior/LinkMe.cs b/Behavior/LinkMe.cs index fcd039b..2c95757 100644 --- a/Behavior/LinkMe.cs +++ b/Behavior/LinkMe.cs @@ -25,7 +25,7 @@ public class LinkMeInitiate : Behavior var lc = new LinkClose(pw, message.Author); Behaver.Behaviors.Add(lc); - await message.Channel.SendMessage($"on your secondary, send me this: !iam {pw}"); + Behaver.Instance.SendMessage(message.Channel.Id, $"on your secondary, send me this: !iam {pw}"); Thread.Sleep(TimeSpan.FromMinutes(5)); Behaver.Behaviors.Remove(lc); @@ -63,22 +63,23 @@ public class LinkClose : Behavior var secondary = message.Author.IsUser; if(_primary.IsUser.Id == secondary.Id) { - await message.Channel.SendMessage("i know :)"); + + Behaver.Instance.SendMessage(message.Channel.Id, "i know :)"); return true; } if(message.Author.IsBot != _primary.IsBot) { - await message.Channel.SendMessage("the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)"); + Behaver.Instance.SendMessage(message.Channel.Id, "the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)"); return true; } if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary)) { - await message.Channel.SendMessage("done :)"); + Behaver.Instance.SendMessage(message.Channel.Id, "done :)"); } else { - await message.Channel.SendMessage("failed :("); + Behaver.Instance.SendMessage(message.Channel.Id, "failed :("); } return true; diff --git a/Behavior/Peptalk.cs b/Behavior/Peptalk.cs deleted file mode 100644 index e222ec7..0000000 --- a/Behavior/Peptalk.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace vassago.Behavior; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using vassago.Models; -using QRCoder; - -[StaticPlz] -public class PepTalk : Behavior -{ - public override string Name => "PepTalk"; - - public override string Trigger => "\\bneeds? (an? )?(peptalk|inspiration|ego-?boost)"; - - public override string Description => "assembles a pep talk from a few pieces"; - - public override async Task ActOn(Message message) - { - var piece1 = new List{ - "Champ, ", - "Fact: ", - "Everybody says ", - "Dang... ", - "Check it: ", - "Just saying.... ", - "Tiger, ", - "Know this: ", - "News alert: ", - "Gurrrrl; ", - "Ace, ", - "Excuse me, but ", - "Experts agree: ", - "imo ", - "using my **advanced ai** i have calculated ", - "k, LISSEN: " - }; - var piece2 = new List{ - "the mere idea of you ", - "your soul ", - "your hair today ", - "everything you do ", - "your personal style ", - "every thought you have ", - "that sparkle in your eye ", - "the essential you ", - "your life's journey ", - "your aura ", - "your presence here ", - "what you got going on ", - "that saucy personality ", - "your DNA ", - "that brain of yours ", - "your choice of attire ", - "the way you roll ", - "whatever your secret is ", - "all I learend from the private data I bought from zucc " - }; - var piece3 = new List{ - "has serious game, ", - "rains magic, ", - "deserves the Nobel Prize, ", - "raises the roof, ", - "breeds miracles, ", - "is paying off big time, ", - "shows mad skills, ", - "just shimmers, ", - "is a national treasure, ", - "gets the party hopping, ", - "is the next big thing, ", - "roars like a lion, ", - "is a rainbow factory, ", - "is made of diamonds, ", - "makes birds sing, ", - "should be taught in school, ", - "makes my world go around, ", - "is 100% legit, " - }; - var piece4 = new List{ - "according to The New England Journal of Medicine.", - "24/7.", - "and that's a fact.", - "you feel me?", - "that's just science.", - "would I lie?", //...can I lie? WHAT AM I, FATHER? (or whatever the quote is from the island of dr moreau) - "for reals.", - "mic drop.", - "you hidden gem.", - "period.", - "hi5. o/", - "so get used to it." - }; - await message.Channel.SendMessage(piece1[Shared.r.Next(piece1.Count)] + piece2[Shared.r.Next(piece2.Count)] + piece3[Shared.r.Next(piece3.Count)] + piece4[Shared.r.Next(piece4.Count)]); - return true; - } -} \ No newline at end of file diff --git a/Behavior/PulseCheck.cs b/Behavior/PulseCheck.cs index fddb6cf..a60e822 100644 --- a/Behavior/PulseCheck.cs +++ b/Behavior/PulseCheck.cs @@ -18,9 +18,9 @@ public class PulseCheck : Behavior public override async Task ActOn(Message message) { if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= 16258) - await message.Channel.SendFile("assets/ekgblip.png", null); + Behaver.Instance.SendFile(message.Channel.Id, "assets/ekgblip.png", null); else - await message.Channel.SendMessage("[lub-dub]"); + Behaver.Instance.SendMessage(message.Channel.Id, "[lub-dub]"); return true; } -} \ No newline at end of file +} diff --git a/Behavior/QRify.cs b/Behavior/QRify.cs index 2e81f6a..43bb2d1 100644 --- a/Behavior/QRify.cs +++ b/Behavior/QRify.cs @@ -21,7 +21,7 @@ public class QRify : Behavior public override bool ShouldAct(Message message) { - if(message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024) + if (message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024) return false; return base.ShouldAct(message); } @@ -42,19 +42,19 @@ public class QRify : Behavior File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg); if (ExternalProcess.GoPlz("convert", $"tmp/qr{todaysnumber}.svg tmp/qr{todaysnumber}.png")) { - if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= (ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)) - await message.Channel.SendFile($"tmp/qr{todaysnumber}.png", null); + if (message.Channel.EffectivePermissions.MaxAttachmentBytes >= (ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)) + Behaver.Instance.SendFile(message.Channel.Id, $"tmp/qr{todaysnumber}.png", null); else - await message.Channel.SendMessage($"resulting qr image 2 big 4 here ({(ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)} / {message.Channel.EffectivePermissions.MaxAttachmentBytes})"); + Behaver.Instance.SendMessage(message.Channel.Id, $"resulting qr image 2 big 4 here ({(ulong)(new System.IO.FileInfo($"tmp / qr{ todaysnumber}.png").Length)} / {message.Channel.EffectivePermissions.MaxAttachmentBytes})"); File.Delete($"tmp/qr{todaysnumber}.svg"); File.Delete($"tmp/qr{todaysnumber}.png"); } else { - await message.Channel.SendMessage("convert failed :( aaaaaaadam!"); + Behaver.Instance.SendMessage(message.Channel.Id, "convert failed :( aaaaaaadam!"); Console.Error.WriteLine($"convert failed :( qr{todaysnumber}"); return false; } return true; } -} \ No newline at end of file +} diff --git a/Behavior/RoomRead.cs b/Behavior/RoomRead.cs index dd78fc5..6d81c34 100644 --- a/Behavior/RoomRead.cs +++ b/Behavior/RoomRead.cs @@ -22,7 +22,7 @@ public class RoomRead : Behavior sb.Append(". Lewdness level: "); sb.Append(message.Channel.EffectivePermissions.LewdnessFilterLevel.GetDescription()); sb.Append("."); - await message.Channel.SendMessage(sb.ToString()); + Behaver.Instance.SendMessage(message.Channel.Id, sb.ToString()); return true; } } diff --git a/Behavior/TwitchSummon.cs b/Behavior/TwitchSummon.cs index 3e9521b..736b0e0 100644 --- a/Behavior/TwitchSummon.cs +++ b/Behavior/TwitchSummon.cs @@ -28,6 +28,14 @@ public class TwitchSummon : Behavior } Rememberer.RememberUAC(myUAC); } + internal static TwitchInterface.TwitchInterface getAnyTwitchInterface() + { + return Shared.ProtocolList.FirstOrDefault(ip => + ip is TwitchInterface.TwitchInterface) + //.FirstOrDefault() + as TwitchInterface.TwitchInterface; + } + public override bool ShouldAct(Message message) { if (!base.ShouldAct(message)) @@ -45,56 +53,57 @@ public class TwitchSummon : Behavior public override async Task ActOn(Message message) { - var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); + var ti = getAnyTwitchInterface(); if (ti != null) { var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim(); - await message.Channel.SendMessage(ti.AttemptJoin(channelTarget)); + Behaver.Instance.SendMessage(message.Channel.Id, ti.AttemptJoin(channelTarget)); } else { - await message.Reply("i don't have a twitch interface running :("); + Behaver.Instance.Reply(message.Id, "i don't have a twitch interface running :("); } return true; } - [StaticPlz] - public class TwitchDismiss : Behavior +} + +[StaticPlz] +public class TwitchDismiss : Behavior +{ + public override string Name => "Twitch Dismiss"; + + public override string Trigger => "begone, @[me]"; + + public override bool ShouldAct(Message message) { - public override string Name => "Twitch Dismiss"; - - public override string Trigger => "begone, @[me]"; - - public override bool ShouldAct(Message message) - { - var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); + var ti = TwitchSummon.getAnyTwitchInterface(); // Console.WriteLine($"TwitchDismiss checking. menions me? {message.MentionsMe}"); - if (message.MentionsMe && - (Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b"))) - { - var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim(); - ti.AttemptLeave(channelTarget); - //TODO: PERMISSION! who can dismiss me? pretty simple list: - //1) anyone in the channel with authority* - //2) whoever summoned me - //* i don't know if the twitch *chat* interface will tell me if someone's a mod. - return true; - } - return false; - } - - public override async Task ActOn(Message message) + if (message.MentionsMe && + (Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b"))) { - var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); - - if (ti != null) - { - ti.AttemptLeave(message.Channel.DisplayName); - } - else - { - await message.Reply("i don't have a twitch interface running :("); - } + var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim(); + ti.AttemptLeave(channelTarget); + //TODO: PERMISSION! who can dismiss me? pretty simple list: + //1) anyone in the channel with authority* + //2) whoever summoned me + //* i don't know if the twitch *chat* interface will tell me if someone's a mod. return true; } + return false; + } + + public override async Task ActOn(Message message) + { + var ti = TwitchSummon.getAnyTwitchInterface(); + + if (ti != null) + { + ti.AttemptLeave(message.Channel.DisplayName); + } + else + { + Behaver.Instance.Reply(message.Id, "i don't have a twitch interface running :("); + } + return true; } } diff --git a/Behavior/UnitConvert.cs b/Behavior/UnitConvert.cs index 969353d..48fa654 100644 --- a/Behavior/UnitConvert.cs +++ b/Behavior/UnitConvert.cs @@ -23,13 +23,13 @@ public class UnitConvert : Behavior if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric)) { Console.WriteLine("let's try and convert..."); - await message.Channel.SendMessage(Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower())); + Behaver.Instance.SendMessage(message.Channel.Id, Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower())); return true; } - await message.Channel.SendMessage("mysteriously semi-parsable"); + Behaver.Instance.SendMessage(message.Channel.Id, "mysteriously semi-parsable"); return true; } - await message.Channel.SendMessage( "unparsable"); + Behaver.Instance.SendMessage(message.Channel.Id, "unparsable"); return true; } } diff --git a/Behavior/Webhook.cs b/Behavior/Webhook.cs index e91dc08..f941850 100644 --- a/Behavior/Webhook.cs +++ b/Behavior/Webhook.cs @@ -160,11 +160,11 @@ public class Webhook : Behavior { var tragedy = $"{response.StatusCode} - {response.ReasonPhrase} - {await response.Content.ReadAsStringAsync()}"; Console.Error.WriteLine(tragedy); - await message.Reply(tragedy); + Behaver.Instance.Reply(message.Id, tragedy); } else { - await message.Reply(await response.Content.ReadAsStringAsync()); + Behaver.Instance.Reply(message.Id, await response.Content.ReadAsStringAsync()); } return true; } diff --git a/Behavior/WishLuck.cs b/Behavior/WishLuck.cs index cf14a6e..3cafa4e 100644 --- a/Behavior/WishLuck.cs +++ b/Behavior/WishLuck.cs @@ -25,9 +25,9 @@ public class WishLuck : Behavior toSend = "\U0001f340";//4-leaf clover } if(message.Channel.EffectivePermissions.ReactionsPossible == true) - await message.React(toSend); + Behaver.Instance.React(message.Id, toSend); else - await message.Channel.SendMessage(toSend); + Behaver.Instance.SendMessage(message.Channel.Id, toSend); return true; } -} \ No newline at end of file +} diff --git a/ConsoleService.cs b/ConsoleService.cs index 158b90d..e56c40f 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -36,7 +36,7 @@ namespace vassago { var d = new DiscordInterface(); initTasks.Add(d.Init(dt)); - ProtocolInterfaces.ProtocolList.discords.Add(d); + Shared.ProtocolList.Add(d); } if (TwitchConfigs?.Any() ?? false) @@ -44,7 +44,7 @@ namespace vassago { var t = new TwitchInterface.TwitchInterface(); initTasks.Add(t.Init(tc)); - ProtocolInterfaces.ProtocolList.twitchs.Add(t); + Shared.ProtocolList.Add(t); } Task.WaitAll(initTasks.ToArray(), cancellationToken); diff --git a/Models/Channel.cs b/Models/Channel.cs index de9bbdc..3ebbed8 100644 --- a/Models/Channel.cs +++ b/Models/Channel.cs @@ -38,13 +38,6 @@ public class Channel //both incoming and outgoing //public Dictionary Aliases { get; set; } - [NonSerialized] - public Func SendFile; - - [NonSerialized] - public Func SendMessage; - - public DefinitePermissionSettings EffectivePermissions { get diff --git a/Models/Message.cs b/Models/Message.cs index 3c909c7..e026795 100644 --- a/Models/Message.cs +++ b/Models/Message.cs @@ -22,11 +22,4 @@ public class Message public List Attachments { get; set; } public Account Author { get; set; } public Channel Channel { get; set; } - -//TODO: these are nicities to make it OOP, but it couples them with their respective platform interfaces (and connections!) - [NonSerialized] - public Func Reply; - - [NonSerialized] - public Func React; } diff --git a/Program.cs b/Program.cs index 1d066ef..b8127d8 100644 --- a/Program.cs +++ b/Program.cs @@ -3,8 +3,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc.NewtonsoftJson; using vassago.Models; -#pragma warning disable CA2254 - var builder = WebApplication.CreateBuilder(args); // Add services to the container. diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 607f9eb..44a21e3 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -15,18 +15,19 @@ using System.Reactive.Linq; namespace vassago.ProtocolInterfaces.DiscordInterface; - //data received - //translate data to internal type - //store - //ship off to behaver +//data received +//translate data to internal type +//store +//ship off to behaver -public class DiscordInterface +public class DiscordInterface : ProtocolInterface { - internal static string PROTOCOL { get => "discord"; } + public static new string Protocol { get => "discord"; } internal DiscordSocketClient client; private bool eventsSignedUp = false; private static readonly SemaphoreSlim discordChannelSetup = new(1, 1); private Channel protocolAsChannel; + public override Channel SelfChannel { get => protocolAsChannel;} public async Task Init(string config) { @@ -52,7 +53,7 @@ public class DiscordInterface try { - protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL); + protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == Protocol); if (protocolAsChannel == null) { protocolAsChannel = new Channel() @@ -65,7 +66,7 @@ public class DiscordInterface LinksAllowed = true, ReactionsPossible = true, ExternalId = null, - Protocol = PROTOCOL, + Protocol = Protocol, SubChannels = [] }; } @@ -74,8 +75,6 @@ public class DiscordInterface Console.WriteLine($"discord, channel with id {protocolAsChannel.Id}, already exists"); } protocolAsChannel.DisplayName = "discord (itself)"; - protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); }; - protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); }; protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel); Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}"); } @@ -206,12 +205,12 @@ public class DiscordInterface } internal Message UpsertMessage(IUserMessage dMessage) { - var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL) + var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == Protocol) ?? new() { //I don't understand why messages need to have their Ids specified but no other entity does. shrug dot emoji Id = Guid.NewGuid(), - Protocol = PROTOCOL + Protocol = Protocol }; if (dMessage.Attachments?.Count > 0) @@ -235,17 +234,15 @@ public class DiscordInterface m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id && (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0)); - m.Reply = (t) => { return dMessage.ReplyAsync(TruncateText(t, m.Channel.MaxTextChars)); }; - m.React = (e) => { return AttemptReact(dMessage, e); }; Rememberer.RememberMessage(m); return m; } internal Channel UpsertChannel(IMessageChannel channel) { - Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); + Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == Protocol); if (c == null) { - Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); + Console.WriteLine($"couldn't find channel under protocol {Protocol} with externalId {channel.Id.ToString()}"); c = new Channel() { Users = [] @@ -255,7 +252,7 @@ public class DiscordInterface c.ExternalId = channel.Id.ToString(); c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal; c.Messages ??= []; - c.Protocol = PROTOCOL; + c.Protocol = Protocol; if (channel is IGuildChannel) { Console.WriteLine($"{channel.Name} is a guild channel. So i'm going to upsert the guild, {(channel as IGuildChannel).Guild}"); @@ -276,9 +273,9 @@ public class DiscordInterface switch (c.ChannelType) { case vassago.Models.Enumerations.ChannelType.DM: - var asPriv =(channel as IPrivateChannel); + var asPriv = (channel as IPrivateChannel); var sender = asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id); // why yes, there's a list of recipients, and it's the sender. - if(sender != null) + if (sender != null) { c.DisplayName = "DM: " + sender.Username; } @@ -295,7 +292,7 @@ public class DiscordInterface Channel parentChannel = null; if (channel is IGuildChannel) { - parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); + parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == Protocol); if (parentChannel is null) { Console.Error.WriteLine("why am I still null?"); @@ -311,19 +308,16 @@ public class DiscordInterface Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } parentChannel.SubChannels ??= []; - if(!parentChannel.SubChannels.Contains(c)) + if (!parentChannel.SubChannels.Contains(c)) { parentChannel.SubChannels.Add(c); } - c.SendMessage = (t) => { return channel.SendMessageAsync(TruncateText(t, c.MaxTextChars));}; - c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; - c = Rememberer.RememberChannel(c); //Console.WriteLine($"no one knows how to make good tooling. c.users.first, which needs client currentuser id tostring. c: {c}, c.Users {c.Users}, client: {client}, client.CurrentUser: {client.CurrentUser}, client.currentUser.Id: {client.CurrentUser.Id}"); var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString()); - if(selfAccountInChannel == null) + if (selfAccountInChannel == null) { selfAccountInChannel = UpsertAccount(client.CurrentUser, c); } @@ -332,10 +326,10 @@ public class DiscordInterface } internal Channel UpsertChannel(IGuild channel) { - Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); + Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == Protocol); if (c == null) { - Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); + Console.WriteLine($"couldn't find channel under protocol {Protocol} with externalId {channel.Id.ToString()}"); c = new Channel(); } @@ -348,9 +342,6 @@ public class DiscordInterface c.SubChannels ??= []; c.MaxAttachmentBytes = channel.MaxUploadLimit; - c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; - c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); }; - return Rememberer.RememberChannel(c); } internal static Account UpsertAccount(IUser discordUser, Channel inChannel) @@ -361,15 +352,16 @@ public class DiscordInterface { Console.WriteLine($"acc's user: {acc.IsUser?.Id}"); } - acc ??= new Account() { - IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == PROTOCOL)) + acc ??= new Account() + { + IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == Protocol)) ?? new User() }; acc.Username = discordUser.Username; acc.ExternalId = discordUser.Id.ToString(); acc.IsBot = discordUser.IsBot || discordUser.IsWebhook; - acc.Protocol = PROTOCOL; + acc.Protocol = Protocol; acc.SeenInChannel = inChannel; Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); @@ -384,7 +376,7 @@ public class DiscordInterface } Rememberer.RememberAccount(acc); inChannel.Users ??= []; - if(!inChannel.Users.Contains(acc)) + if (!inChannel.Users.Contains(acc)) { inChannel.Users.Add(acc); Rememberer.RememberChannel(inChannel); @@ -392,36 +384,112 @@ public class DiscordInterface return acc; } - private static Task AttemptReact(IUserMessage msg, string e) + private static async Task AttemptReact(IUserMessage msg, string e) { var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString()); //var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides var preferredEmote = e; if (Emoji.TryParse(preferredEmote, out Emoji emoji)) { - return msg.AddReactionAsync(emoji); + msg.AddReactionAsync(emoji); + return 200; } if (!Emote.TryParse(preferredEmote, out Emote emote)) { if (preferredEmote == e) Console.Error.WriteLine($"never heard of emote {e}"); - return Task.CompletedTask; + return 405; } - return msg.AddReactionAsync(emote); + msg.AddReactionAsync(emote); + return 200; } private static string TruncateText(string msg, uint? chars) { chars ??= 500; - if(msg?.Length > chars) + if (msg?.Length > chars) { - return msg.Substring(0, (int)chars-2) + "✂"; + return msg.Substring(0, (int)chars - 2) + "✂"; } else { return msg; } } + public override async Task SendMessage(Channel channel, string text) + { + var dcCh = await client.GetChannelAsync(ulong.Parse(channel.ExternalId)); + if (dcCh == null) + { + return 404; + } + if (dcCh is IMessageChannel msgChannel) + { + await msgChannel.SendMessageAsync(TruncateText(text, channel.MaxTextChars)); + return 200; + } + else + { + return 503; + } + } + public override async Task SendFile(Channel channel, string path, string accompanyingText) + { + var dcCh = await client.GetChannelAsync(ulong.Parse(channel.ExternalId)); + if (dcCh == null) + { + return 404; + } + + if (dcCh is IMessageChannel msgChannel) + { + await msgChannel.SendFileAsync(path, TruncateText(accompanyingText, channel.MaxTextChars)); + return 200; + } + else + { + return 503; + } + } + public override async Task React(Message message, string reaction) + { + var dcCh = await client.GetChannelAsync(ulong.Parse(message.Channel.ExternalId)); + if (dcCh == null) + return 404; + + if (dcCh is IMessageChannel msgChannel) + { + var dcMsg = await msgChannel.GetMessageAsync(ulong.Parse(message.ExternalId)); + if (dcMsg == null) + return 404; + + return await AttemptReact(dcMsg as IUserMessage, reaction); + } + else + { + return 503; + } + } + public override async Task Reply(Message message, string text) + { + var dcCh = await client.GetChannelAsync(ulong.Parse(message.Channel.ExternalId)); + if (dcCh == null) + return 404; + + if (dcCh is IMessageChannel msgChannel) + { + var dcMsg = await msgChannel.GetMessageAsync(ulong.Parse(message.ExternalId)); + if (dcMsg == null) + return 404; + + (dcMsg as IUserMessage).ReplyAsync(TruncateText(text, message.Channel.MaxTextChars)); + return 200; + } + else + { + return 503; + } + } } diff --git a/ProtocolInterfaces/ProtocolInterface.cs b/ProtocolInterfaces/ProtocolInterface.cs new file mode 100644 index 0000000..d31c063 --- /dev/null +++ b/ProtocolInterfaces/ProtocolInterface.cs @@ -0,0 +1,13 @@ +namespace vassago.ProtocolInterfaces; + +using vassago.Models; + +public abstract class ProtocolInterface +{ + public static string Protocol { get; } + public abstract Channel SelfChannel { get; } + public abstract Task SendMessage(Channel channel, string text); + public abstract Task SendFile(Channel channel, string path, string accompanyingText); + public abstract Task React(Message message, string reaction); + public abstract Task Reply(Message message, string text); +} diff --git a/ProtocolInterfaces/ProtocolList.cs b/ProtocolInterfaces/ProtocolList.cs deleted file mode 100644 index 69e64ea..0000000 --- a/ProtocolInterfaces/ProtocolList.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace vassago.ProtocolInterfaces; - -public static class ProtocolList -{ - public static List discords = new(); - public static List twitchs = new(); -} \ No newline at end of file diff --git a/ProtocolInterfaces/TwitchInterface/TwitchInterface.cs b/ProtocolInterfaces/TwitchInterface/TwitchInterface.cs index a356e0d..05022b8 100644 --- a/ProtocolInterfaces/TwitchInterface/TwitchInterface.cs +++ b/ProtocolInterfaces/TwitchInterface/TwitchInterface.cs @@ -10,19 +10,16 @@ using TwitchLib.Communication.Clients; using TwitchLib.Communication.Models; using vassago.Behavior; using vassago.Models; +using vassago.ProtocolInterfaces; namespace vassago.TwitchInterface; -internal class unifiedTwitchMessage +public class TwitchInterface : ProtocolInterface { - public unifiedTwitchMessage(ChatMessage chatMessage) { } -} - -public class TwitchInterface -{ - internal const string PROTOCOL = "twitch"; + public static new string Protocol { get => "twitch"; } private static SemaphoreSlim channelSetupSemaphpore = new SemaphoreSlim(1, 1); private Channel protocolAsChannel; + public override Channel SelfChannel { get => protocolAsChannel;} private Account selfAccountInProtocol; TwitchClient client; @@ -32,7 +29,7 @@ public class TwitchInterface try { - protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL); + protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == Protocol); if (protocolAsChannel == null) { protocolAsChannel = new Channel() @@ -45,12 +42,10 @@ public class TwitchInterface LinksAllowed = false, ReactionsPossible = false, ExternalId = null, - Protocol = PROTOCOL, + Protocol = Protocol, SubChannels = [] }; protocolAsChannel.DisplayName = "twitch (itself)"; - protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"twitch itself cannot accept text"); }; - protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"twitch itself cannot send file"); }; protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel); Console.WriteLine($"protocol as channle added; {protocolAsChannel}"); } @@ -97,7 +92,8 @@ public class TwitchInterface //translate to internal, upsert var m = UpsertMessage(e.WhisperMessage); - m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(e.WhisperMessage.Username, t); }); }; + //can't send whispers without giving up cellphone number. + //m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(e.WhisperMessage.Username, t); }); }; m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM; //act on await Behaver.Instance.ActOn(m); @@ -112,7 +108,6 @@ public class TwitchInterface //translate to internal, upsert var m = UpsertMessage(e.ChatMessage); - m.Reply = (t) => { return Task.Run(() => { client.SendReply(e.ChatMessage.Channel, e.ChatMessage.Id, t); }); }; m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.Normal; //act on await Behaver.Instance.ActOn(m); @@ -152,14 +147,14 @@ public class TwitchInterface acc ??= new Account() { IsUser = Rememberer.SearchUser( - u => u.Accounts.Any(a => a.ExternalId == username && a.Protocol == PROTOCOL)) + u => u.Accounts.Any(a => a.ExternalId == username && a.Protocol == Protocol)) ?? new vassago.Models.User() }; acc.Username = username; acc.ExternalId = username; //acc.IsBot = false? there is a way to tell, but you have to go back through the API - acc.Protocol = PROTOCOL; + acc.Protocol = Protocol; acc.SeenInChannel = inChannel; // Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); @@ -185,7 +180,7 @@ public class TwitchInterface private Channel UpsertChannel(string channelName) { Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channelName - && ci.Protocol == PROTOCOL); + && ci.Protocol == Protocol); if (c == null) { // Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channelName}"); @@ -199,11 +194,9 @@ public class TwitchInterface c.ExternalId = channelName; c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal; c.Messages ??= []; - c.Protocol = PROTOCOL; + c.Protocol = Protocol; c.ParentChannel = protocolAsChannel; c.SubChannels = c.SubChannels ?? new List(); - c.SendMessage = (t) => { return Task.Run(() => { client.SendMessage(channelName, t); }); }; - c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); }; c = Rememberer.RememberChannel(c); var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId); @@ -217,7 +210,7 @@ public class TwitchInterface private Channel UpsertDMChannel(string whisperWith) { Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == $"w_{whisperWith}" - && ci.Protocol == PROTOCOL); + && ci.Protocol == Protocol); if (c == null) { // Console.WriteLine($"couldn't find channel under protocol {PROTOCOL}, whisper with {whisperWith}"); @@ -231,25 +224,9 @@ public class TwitchInterface c.ExternalId = $"w_{whisperWith}"; c.ChannelType = vassago.Models.Enumerations.ChannelType.DM; c.Messages ??= []; - c.Protocol = PROTOCOL; + c.Protocol = Protocol; c.ParentChannel = protocolAsChannel; c.SubChannels = c.SubChannels ?? new List(); - c.SendMessage = (t) => - { - return Task.Run(() => - { - try - { - - client.SendWhisper(whisperWith, t); - } - catch (Exception e) - { - Console.Error.WriteLine(e); - } - }); - }; - c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); }; c = Rememberer.RememberChannel(c); var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId); @@ -266,10 +243,10 @@ public class TwitchInterface //none of the features we care about are on it! private Message UpsertMessage(ChatMessage chatMessage) { - var m = Rememberer.SearchMessage(mi => mi.ExternalId == chatMessage.Id && mi.Protocol == PROTOCOL) + var m = Rememberer.SearchMessage(mi => mi.ExternalId == chatMessage.Id && mi.Protocol == Protocol) ?? new() { - Protocol = PROTOCOL, + Protocol = Protocol, Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc) }; m.Content = chatMessage.Message; @@ -277,8 +254,6 @@ public class TwitchInterface m.Channel = UpsertChannel(chatMessage.Channel); m.Author = UpsertAccount(chatMessage.Username, m.Channel); m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b"); - m.Reply = (t) => { return Task.Run(() => { client.SendReply(chatMessage.Channel, chatMessage.Id, t); }); }; - m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); }; Rememberer.RememberMessage(m); return m; } @@ -288,11 +263,11 @@ public class TwitchInterface private Message UpsertMessage(WhisperMessage whisperMessage) { //WhisperMessage.Id corresponds to chatMessage.Id. \*eye twitch* - var m = Rememberer.SearchMessage(mi => mi.ExternalId == whisperMessage.MessageId && mi.Protocol == PROTOCOL) + var m = Rememberer.SearchMessage(mi => mi.ExternalId == whisperMessage.MessageId && mi.Protocol == Protocol) ?? new() { Id = Guid.NewGuid(), - Protocol = PROTOCOL, + Protocol = Protocol, Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc) }; m.Content = whisperMessage.Message; @@ -300,8 +275,6 @@ public class TwitchInterface m.Channel = UpsertDMChannel(whisperMessage.Username); m.Author = UpsertAccount(whisperMessage.Username, m.Channel); m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b"); - m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(whisperMessage.Username, t); }); }; - m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); }; Rememberer.RememberMessage(m); return m; } @@ -317,4 +290,22 @@ public class TwitchInterface client.SendMessage(channelTarget, "o7"); client.LeaveChannel(channelTarget); } + public override async Task SendMessage(Channel channel, string text) + { + Task.Run(() => { client.SendMessage(channel.ExternalId, text); }); + return 200; + } + public override async Task SendFile(Channel channel, string path, string accompanyingText) + { + return 405; + } + public override async Task React(Message message, string reaction) + { + return 405; + } + public override async Task Reply(Message message, string text) + { + Task.Run(() => { client.SendReply(message.Channel.ExternalId, message.ExternalId, text); }); + return 200; + } } diff --git a/Rememberer.cs b/Rememberer.cs index 5d7140c..bcff5ed 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -124,14 +124,14 @@ public static class Rememberer { if (toForget.SubChannels?.Count > 0) { - foreach (var childChannel in toForget.SubChannels) + foreach (var childChannel in toForget.SubChannels.ToList()) { ForgetChannel(childChannel); } } if(toForget.Users?.Count > 0) { - foreach(var account in toForget.Users) + foreach(var account in toForget.Users.ToList()) { ForgetAccount(account); } diff --git a/Shared.cs b/Shared.cs index 15068d4..aebc76e 100644 --- a/Shared.cs +++ b/Shared.cs @@ -3,7 +3,7 @@ namespace vassago; using System; using System.Net.Http; using vassago.Models; - +using vassago.ProtocolInterfaces; public static class Shared { @@ -12,4 +12,5 @@ public static class Shared public static HttpClient HttpClient { get; internal set; } = new HttpClient(); public static bool SetupSlashCommands { get; set; } public static Uri API_URL {get;set;} + public static List ProtocolList { get; set; } = new(); } diff --git a/WebInterface/Controllers/ChannelsController.cs b/WebInterface/Controllers/ChannelsController.cs index 44efc0c..188d0f1 100644 --- a/WebInterface/Controllers/ChannelsController.cs +++ b/WebInterface/Controllers/ChannelsController.cs @@ -20,7 +20,11 @@ public class ChannelsController() : Controller //but that would take in all the messages. //realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel. - var channel = allChannels.First(u => u.Id == id); + var channel = allChannels.FirstOrDefault(u => u.Id == id); + if(channel == null) + { + return Problem("couldn't find that channle"); + } var walker = channel; while(walker != null) { @@ -58,4 +62,4 @@ public class ChannelsController() : Controller { return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } -} \ No newline at end of file +} diff --git a/WebInterface/Controllers/api/InternalAPIProtocolController.cs b/WebInterface/Controllers/api/InternalAPIProtocolController.cs new file mode 100644 index 0000000..94c5529 --- /dev/null +++ b/WebInterface/Controllers/api/InternalAPIProtocolController.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago.Models; +using vassago.ProtocolInterfaces.DiscordInterface; + +namespace vassago.Controllers.api; + +[Route("api/[controller]")] +[ApiController] +public class InternalAPIProtocolController : ControllerBase +{ + private readonly ILogger _logger; + + public InternalAPIProtocolController(ILogger logger) + { + _logger = logger; + } + + public class extraSpecialObjectReadGlorifiedTupleFor_PostMessage + { + public string messageText; + public Guid channelId; + } + [HttpPost] + [Route("PostMessage")] + [Produces("application/json")] + public IActionResult PostMessage([FromBody]extraSpecialObjectReadGlorifiedTupleFor_PostMessage param) + { + return StatusCode(Behaver.Instance.SendMessage(param.channelId, param.messageText).Result); + } + public class extraSpecialObjectReadGlorifiedTupleFor_ReplyToMessage + { + public string messageText; + public Guid repliedMessageId; + } + [HttpPost] + [Route("ReplyToMessage")] + [Produces("application/json")] + public IActionResult ReplyToMessage([FromBody] extraSpecialObjectReadGlorifiedTupleFor_ReplyToMessage param) + { + Console.WriteLine("ReplyToMessage - ${param.repliedMessageId}, {param.messageText}"); + return StatusCode(Behaver.Instance.Reply(param.repliedMessageId, param.messageText).Result); + } +} diff --git a/WebInterface/Controllers/api/RemembererController.cs b/WebInterface/Controllers/api/RemembererController.cs index efb24a5..9b43427 100644 --- a/WebInterface/Controllers/api/RemembererController.cs +++ b/WebInterface/Controllers/api/RemembererController.cs @@ -32,7 +32,7 @@ public class RemembererController : ControllerBase return Rememberer.AttachmentDetail(id); } [HttpPut] - [Route("Channel")] + [Route("Channels")] [Produces("application/json")] public Channel CreateChannel(Guid id) { @@ -75,7 +75,7 @@ public class RemembererController : ControllerBase return Rememberer.AttachmentDetail(id); } [HttpGet] - [Route("Channel")] + [Route("Channels")] [Produces("application/json")] public Channel GetChannel(Guid id) { @@ -104,7 +104,7 @@ public class RemembererController : ControllerBase } //Update [HttpPatch] - [Route("Channel")] + [Route("Channels")] [Produces("application/json")] public IActionResult Patch([FromBody] Channel channel) { @@ -154,21 +154,23 @@ public class RemembererController : ControllerBase return Ok(); } [HttpDelete] - [Route("Channel")] + [Route("Channels/{id}")] [Produces("application/json")] public IActionResult DeleteChannel(Guid id) { var fromDb = Rememberer.ChannelDetail(id); + _logger.LogDebug($"delete channel {id}"); if (fromDb == null) { _logger.LogError($"attempt to delete channel {id}, not found"); return NotFound(); } Rememberer.ForgetChannel(fromDb); + _logger.LogDebug($"delete channel {id} success"); return Ok(); } [HttpDelete] - [Route("Message")] + [Route("Message/{id}")] [Produces("application/json")] public IActionResult DeleteMessage(Guid id) { @@ -182,7 +184,7 @@ public class RemembererController : ControllerBase return Ok(); } [HttpDelete] - [Route("UAC")] + [Route("UAC/{id}")] [Produces("application/json")] public IActionResult DeleteUAC(Guid id) { @@ -196,7 +198,7 @@ public class RemembererController : ControllerBase return Ok(); } [HttpDelete] - [Route("User")] + [Route("User/{id}")] [Produces("application/json")] public IActionResult DeleteUser(Guid id) { diff --git a/WebInterface/Views/Channels/Details.cshtml b/WebInterface/Views/Channels/Details.cshtml index fbdd57b..d218a0e 100644 --- a/WebInterface/Views/Channels/Details.cshtml +++ b/WebInterface/Views/Channels/Details.cshtml @@ -24,7 +24,7 @@ Lewdness Filter Level - ⤵ inherited - @Enumerations.GetDescription(IfInheritedLewdnessFilterLevel) @foreach (Enumerations.LewdnessFilterLevel enumVal in Enum.GetValues(typeof(Enumerations.LewdnessFilterLevel))) @@ -54,7 +54,7 @@ Meanness Filter Level - ⤵ inherited - @Enumerations.GetDescription(IfInheritedMeannessFilterLevel) @foreach (Enumerations.MeannessFilterLevel enumVal in Enum.GetValues(typeof(Enumerations.MeannessFilterLevel))) @@ -135,7 +135,7 @@ function forget(){ console.log("here we go"); if(window.confirm("delete? really really?") == true){ - deleteModel(jsonifyChannel(), '/api/Channels/'); + deleteModel(jsonifyChannel().Id, window.history.back); } } @@ -149,7 +149,7 @@ var sb = new StringBuilder(); sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: ["); var first = true; - foreach (var acc in ThisChannel.Users.OrderBy(a => a.SeenInChannel.LineageSummary)) + foreach (var acc in ThisChannel.Users?.OrderBy(a => a?.SeenInChannel?.LineageSummary)) { if(!first) sb.Append(','); @@ -166,4 +166,4 @@ $('#accountsTree').bstreeview({ data: accountsTree() }); -} \ No newline at end of file +} diff --git a/WebInterface/Views/Users/Details.cshtml b/WebInterface/Views/Users/Details.cshtml index 86a5a5d..b652674 100644 --- a/WebInterface/Views/Users/Details.cshtml +++ b/WebInterface/Views/Users/Details.cshtml @@ -11,7 +11,7 @@ Display Name (here) + onclick="patchModel(jsonifyUser())" disabled alt"todo">update Accounts @@ -54,12 +54,12 @@ } sb.Append("]}]"); } - console.log(@Html.Raw(sb.ToString())); + console.log(@Html.Raw(sb.ToString())); var tree = @Html.Raw(sb.ToString()); return tree; } $('#accountsTree').bstreeview({ data: getAccountsTree() }); - document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } }); + document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser()) } }); } diff --git a/vassago.csproj b/vassago.csproj index 7ceaff9..15bfd46 100644 --- a/vassago.csproj +++ b/vassago.csproj @@ -3,7 +3,11 @@ net8.0 enable - $(NoWarn);CA2254 + $(NoWarn);CS1998;CS4014 + + + diff --git a/wwwroot/js/site.js b/wwwroot/js/site.js index 8286404..4ef597b 100644 --- a/wwwroot/js/site.js +++ b/wwwroot/js/site.js @@ -8,7 +8,7 @@ function Account(displayName, accountId, protocol){ //todo: figure out what the URL actually needs to be, rather than assuming you get a whole-ass server to yourself. //you selfish fuck... What are you, fox? //as it stands, you want something like /api/Channels/, trailing slash intentional -function patchModel(model, deprecated_apiUrl) +function patchModel(model, callback) { //structure the model your (dang) self into a nice object console.log(model); @@ -22,7 +22,7 @@ function patchModel(model, deprecated_apiUrl) // var id=components[3]; console.log(JSON.stringify(model)); - fetch(apiUrl + type + '/', { + fetch(apiUrl + 'Rememberer/' + type + '/', { method: 'PATCH', headers: { 'Content-Type': 'application/json', @@ -44,22 +44,17 @@ function patchModel(model, deprecated_apiUrl) }); } -function deleteModel(model, deprecated_apiUrl) +function deleteModel(id, callback) { var components = window.location.pathname.split('/'); - // if(components[2] !== "Details") - // { - // console.log("wtf are you doing? " + components[2] + " is something other than Details"); - // } var type=components[1]; let result = null; - // var id=components[3]; - fetch(apiUrl + type + '/', { + var id=components[3]; + fetch(apiUrl + 'Rememberer/' + type + '/' + id, { method: 'DELETE', headers: { 'Content-Type': 'application/json', - }, - body: JSON.stringify(model), + } }) .then(response => { if (!response.ok) {