From 92988257b6273802593f27a1e6218f505a03f81f Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 20 Jun 2025 20:09:44 -0400 Subject: [PATCH] localizations. command aliases. see #19. see #9. --- Behaver.cs | 21 +- Behavior/Behavior.cs | 12 +- Behavior/DefinitionSnarkCogDiss.cs | 4 +- Behavior/DefinitionSnarkGaslight.cs | 4 +- Behavior/Detiktokify.cs | 6 +- Behavior/FiximageHeic.cs | 2 +- Behavior/GeneralSnarkCloudNative.cs | 6 +- Behavior/GeneralSnarkGooglit.cs | 4 +- Behavior/GeneralSnarkMisspellDefinitely.cs | 6 +- Behavior/GeneralSnarkPlaying.cs | 4 +- Behavior/Gratitude.cs | 4 +- Behavior/Joke.cs | 6 +- Behavior/LinkMe.cs | 2 +- Behavior/QRify.cs | 4 +- Behavior/TwitchSummon.cs | 6 +- Behavior/UnitConvert.cs | 2 +- Behavior/Webhook.cs | 4 +- ConsoleService.cs | 1 + Migrations/20250620023827_locales.Designer.cs | 391 +++++++++++++++++ Migrations/20250620023827_locales.cs | 40 ++ ...50620230813_TranslatedMessages.Designer.cs | 392 ++++++++++++++++++ .../20250620230813_TranslatedMessages.cs | 28 ++ Migrations/ChattingContextModelSnapshot.cs | 11 +- Models/FranzMessage.cs | 6 +- Models/Message.cs | 1 + Models/UAC.cs | 4 +- .../Controllers/api/UACsController.cs | 35 ++ WebInterface/Views/UACs/Details.cshtml | 213 ++++++---- wwwroot/js/site.js | 50 +++ 29 files changed, 1148 insertions(+), 121 deletions(-) create mode 100644 Migrations/20250620023827_locales.Designer.cs create mode 100644 Migrations/20250620023827_locales.cs create mode 100644 Migrations/20250620230813_TranslatedMessages.Designer.cs create mode 100644 Migrations/20250620230813_TranslatedMessages.cs diff --git a/Behaver.cs b/Behaver.cs index cd16a9f..34c3927 100644 --- a/Behaver.cs +++ b/Behaver.cs @@ -45,11 +45,21 @@ public class Behaver { //TODO: this is yet another hit to the database, and a big one. cache them in memory! there needs to be a feasibly-viewable amount, anyway. var matchingUACs = Rememberer.MatchUACs(message); + message.TranslatedContent = message.Content; + foreach (var uacMatch in matchingUACs) + { + uacMatch.Translations ??= []; + uacMatch.CommandAlterations ??= []; + foreach (var localization in uacMatch.Translations) //honestly, i'm *still* mad that foreach thing in null is an exception. in what world is "if not null then" assumed? + { + var r = new Regex(localization.Key); + message.TranslatedContent = r.Replace(message.TranslatedContent, localization.Value); + } + } var behaviorsActedOn = new List(); foreach (var behavior in Behaviors.ToList()) { - //if (!behavior.ShouldAct(message, matchingUACs)) //TODO: this way - if (!behavior.ShouldAct(message)) + if (!behavior.ShouldAct(message, matchingUACs)) { continue; } @@ -58,7 +68,7 @@ public class Behaver behaviorsActedOn.Add(behavior.ToString()); Console.WriteLine("acted on, moving forward"); } - if (message.ActedOn == false && message.MentionsMe && message.Content.Contains('?') && !Behaver.Instance.SelfAccounts.Any(acc => acc.Id == message.Author.Id)) + if (message.ActedOn == false && message.MentionsMe && message.TranslatedContent.Contains('?') && !Behaver.Instance.SelfAccounts.Any(acc => acc.Id == message.Author.Id)) { Console.WriteLine("providing bullshit nonanswer / admitting uselessness"); var responses = new List(){ @@ -81,7 +91,8 @@ public class Behaver Api_Uri = Shared.API_URL, MessageId = message.Id, - MessageContent = message.Content, + Content = message.TranslatedContent, + RawContent = message.Content, MentionsMe = message.MentionsMe, Timestamp = message.Timestamp, AttachmentCount = (uint)(message.Attachments?.Count() ?? 0), @@ -99,7 +110,9 @@ public class Behaver UAC_Matches = matchingUACs.Select(uac => uac.Id).ToList(), BehavedOnBy = actedOnBy }; + Console.WriteLine("producing message"); Telefranz.Instance.ProduceMessage(kafkaesque); + Console.WriteLine("survived producing message"); } internal bool IsSelf(Guid AccountId) diff --git a/Behavior/Behavior.cs b/Behavior/Behavior.cs index 443908f..1b923a7 100644 --- a/Behavior/Behavior.cs +++ b/Behavior/Behavior.cs @@ -13,11 +13,19 @@ public abstract class Behavior //recommendation: set up your UACs in your constructor. public abstract Task ActOn(Message message); - public virtual bool ShouldAct(Message message) + public virtual bool ShouldAct(Message message, List matchedUACs) { if(Behaver.Instance.IsSelf(message.Author.Id)) return false; - return Regex.IsMatch(message.Content, $"{Trigger}\\b", RegexOptions.IgnoreCase); + var triggerTarget = Trigger ; + foreach(var uacMatch in matchedUACs) + { + foreach(var substitution in uacMatch.CommandAlterations) + { + triggerTarget = new Regex(substitution.Key).Replace(triggerTarget, substitution.Value); + } + } + return Regex.IsMatch(message.TranslatedContent, $"{triggerTarget}\\b", RegexOptions.IgnoreCase); } public abstract string Name { get; } diff --git a/Behavior/DefinitionSnarkCogDiss.cs b/Behavior/DefinitionSnarkCogDiss.cs index 551c78d..8422262 100644 --- a/Behavior/DefinitionSnarkCogDiss.cs +++ b/Behavior/DefinitionSnarkCogDiss.cs @@ -18,12 +18,12 @@ public class DefinitionSnarkCogDiss : Behavior public override string Description => "snarkiness about the rampant misuse of the term cognitive dissonance"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium) return false; - return base.ShouldAct(message); + return base.ShouldAct(message, matchedUACs); } public override async Task ActOn(Message message) diff --git a/Behavior/DefinitionSnarkGaslight.cs b/Behavior/DefinitionSnarkGaslight.cs index 00d7333..bf16e05 100644 --- a/Behavior/DefinitionSnarkGaslight.cs +++ b/Behavior/DefinitionSnarkGaslight.cs @@ -18,12 +18,12 @@ public class DefinitionSnarkGaslight : Behavior public override string Description => "snarkiness about the rampant misuse of the term gaslighting"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Unrestricted) return false; - return base.ShouldAct(message); + return base.ShouldAct(message, matchedUACs); } public override async Task ActOn(Message message) diff --git a/Behavior/Detiktokify.cs b/Behavior/Detiktokify.cs index c113e0a..cd2a453 100644 --- a/Behavior/Detiktokify.cs +++ b/Behavior/Detiktokify.cs @@ -24,7 +24,7 @@ public class Detiktokify : Behavior ytdl.OutputFolder = ""; ytdl.OutputFileTemplate = "tiktokbad.%(ext)s"; } - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if (Behaver.Instance.IsSelf(message.Author.Id)) @@ -33,7 +33,7 @@ public class Detiktokify : Behavior if (message.Channel.EffectivePermissions.MaxAttachmentBytes == 0) return false; - var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries); + var wordLikes = message.TranslatedContent.Split(' ', StringSplitOptions.TrimEntries); var possibleLinks = wordLikes?.Where(wl => Uri.IsWellFormedUriString(wl, UriKind.Absolute)).Select(wl => new Uri(wl)); if (possibleLinks != null && possibleLinks.Count() > 0) { @@ -47,7 +47,7 @@ public class Detiktokify : Behavior } if (tiktokLinks.Any()) { - Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}"); + Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.TranslatedContent}"); } return tiktokLinks.Any(); } diff --git a/Behavior/FiximageHeic.cs b/Behavior/FiximageHeic.cs index 8ce20b6..192fbb4 100644 --- a/Behavior/FiximageHeic.cs +++ b/Behavior/FiximageHeic.cs @@ -19,7 +19,7 @@ public class FiximageHeic : Behavior public override string Description => "convert heic images to jpg"; private List heics = new List(); - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if(Behaver.Instance.IsSelf(message.Author.Id)) return false; diff --git a/Behavior/GeneralSnarkCloudNative.cs b/Behavior/GeneralSnarkCloudNative.cs index 45946ba..8eeb556 100644 --- a/Behavior/GeneralSnarkCloudNative.cs +++ b/Behavior/GeneralSnarkCloudNative.cs @@ -17,7 +17,7 @@ public class GeneralSnarkCloudNative : Behavior public override string Name => "general snarkiness: cloud native"; public override string Trigger => "certain tech buzzwords that no human uses in normal conversation"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if (Behaver.Instance.IsSelf(message.Author.Id)) return false; @@ -28,8 +28,8 @@ public class GeneralSnarkCloudNative : Behavior if ((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium) return false; - return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) || - Regex.IsMatch(message.Content, "\\benterprise( |-)?(level|solution)\\b", RegexOptions.IgnoreCase); + return Regex.IsMatch(message.TranslatedContent, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) || + Regex.IsMatch(message.TranslatedContent, "\\benterprise( |-)?(level|solution)\\b", RegexOptions.IgnoreCase); } public override async Task ActOn(Message message) diff --git a/Behavior/GeneralSnarkGooglit.cs b/Behavior/GeneralSnarkGooglit.cs index 53bfc90..712b97b 100644 --- a/Behavior/GeneralSnarkGooglit.cs +++ b/Behavior/GeneralSnarkGooglit.cs @@ -18,12 +18,12 @@ public class GeneralSnarkGooglit : Behavior public override string Description => "snarkiness about how research is not a solved problem"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if (Behaver.Instance.IsSelf(message.Author.Id)) return false; - return Regex.IsMatch(message.Content, $"(just )?google( (it|that|things|before))\\b", RegexOptions.IgnoreCase); + return Regex.IsMatch(message.TranslatedContent, $"(just )?google( (it|that|things|before))\\b", RegexOptions.IgnoreCase); } public override async Task ActOn(Message message) diff --git a/Behavior/GeneralSnarkMisspellDefinitely.cs b/Behavior/GeneralSnarkMisspellDefinitely.cs index a027093..f34993b 100644 --- a/Behavior/GeneralSnarkMisspellDefinitely.cs +++ b/Behavior/GeneralSnarkMisspellDefinitely.cs @@ -32,7 +32,7 @@ public class GeneralSnarkMisspellDefinitely : Behavior {"defineatly", "only the gods know"}, {"definitly", "unless someone cute shows up"} }; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if(Behaver.Instance.IsSelf(message.Author.Id)) return false; @@ -42,7 +42,7 @@ public class GeneralSnarkMisspellDefinitely : Behavior foreach(var k in snarkmap.Keys) { - if( Regex.IsMatch(message.Content?.ToLower(), "\\b"+k+"\\b", RegexOptions.IgnoreCase)) + if( Regex.IsMatch(message.TranslatedContent?.ToLower(), "\\b"+k+"\\b", RegexOptions.IgnoreCase)) return true; } return false; @@ -51,7 +51,7 @@ public class GeneralSnarkMisspellDefinitely : Behavior { foreach(var k in snarkmap.Keys) { - if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase)) + if( Regex.IsMatch(message.TranslatedContent, "\\b"+k+"\\b", RegexOptions.IgnoreCase)) { Behaver.Instance.Reply(message.Id, k + "? so... " + snarkmap[k] + "?"); return true; diff --git a/Behavior/GeneralSnarkPlaying.cs b/Behavior/GeneralSnarkPlaying.cs index bd48ddc..1e729dd 100644 --- a/Behavior/GeneralSnarkPlaying.cs +++ b/Behavior/GeneralSnarkPlaying.cs @@ -19,7 +19,7 @@ public class GeneralSnarkPlaying : Behavior public override string Description => "I didn't think you were playing, but now I do"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if(Behaver.Instance.IsSelf(message.Author.Id)) return false; @@ -28,7 +28,7 @@ public class GeneralSnarkPlaying : Behavior (LewdnessFilterLevel)message.Channel.EffectivePermissions.LewdnessFilterLevel < LewdnessFilterLevel.Moderate) return false; - return Regex.IsMatch(message.Content, "^(s?he|(yo)?u|y'?all|they) thinks? i'?m (playin|jokin|kiddin)g?$", RegexOptions.IgnoreCase); + return Regex.IsMatch(message.TranslatedContent, "^(s?he|(yo)?u|y'?all|they) thinks? i'?m (playin|jokin|kiddin)g?$", RegexOptions.IgnoreCase); } public override async Task ActOn(Message message) { diff --git a/Behavior/Gratitude.cs b/Behavior/Gratitude.cs index 2e47f1a..8922313 100644 --- a/Behavior/Gratitude.cs +++ b/Behavior/Gratitude.cs @@ -16,12 +16,12 @@ public class Gratitude : Behavior public override string Trigger => "thank me"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if(Behaver.Instance.IsSelf(message.Author.Id)) return false; - return Regex.IsMatch(message.Content, "\\bthank (yo)?u\\b", RegexOptions.IgnoreCase) && message.MentionsMe; + return Regex.IsMatch(message.TranslatedContent, "\\bthank (yo)?u\\b", RegexOptions.IgnoreCase) && message.MentionsMe; } public override async Task ActOn(Message message) { diff --git a/Behavior/Joke.cs b/Behavior/Joke.cs index d1b921e..8ce6652 100644 --- a/Behavior/Joke.cs +++ b/Behavior/Joke.cs @@ -65,13 +65,13 @@ public class LaughAtOwnJoke : Behavior { _punchline = punchline; } - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if (Behaver.Instance.IsSelf(message.Author.Id)) return false; - Console.WriteLine($"{message.Content} == {_punchline}"); - return message.Content == _punchline + Console.WriteLine($"{message.TranslatedContent} == {_punchline}"); + return message.TranslatedContent == _punchline && Behaver.Instance.IsSelf(message.Author.Id); } diff --git a/Behavior/LinkMe.cs b/Behavior/LinkMe.cs index 2c95757..21955fc 100644 --- a/Behavior/LinkMe.cs +++ b/Behavior/LinkMe.cs @@ -50,7 +50,7 @@ public class LinkClose : Behavior _primary = primary; } - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { return message.Content == $"!iam {_pw}"; } diff --git a/Behavior/QRify.cs b/Behavior/QRify.cs index 43bb2d1..4413547 100644 --- a/Behavior/QRify.cs +++ b/Behavior/QRify.cs @@ -19,11 +19,11 @@ public class QRify : Behavior public override string Description => "generate text QR codes"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { if (message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024) return false; - return base.ShouldAct(message); + return base.ShouldAct(message, matchedUACs); } public override async Task ActOn(Message message) diff --git a/Behavior/TwitchSummon.cs b/Behavior/TwitchSummon.cs index 736b0e0..0194b54 100644 --- a/Behavior/TwitchSummon.cs +++ b/Behavior/TwitchSummon.cs @@ -36,9 +36,9 @@ public class TwitchSummon : Behavior as TwitchInterface.TwitchInterface; } - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { - if (!base.ShouldAct(message)) + if (!base.ShouldAct(message, matchedUACs)) return false; var uacConf = Rememberer.SearchUAC(uac => uac.OwnerId == uacID); if (uacConf == null) @@ -74,7 +74,7 @@ public class TwitchDismiss : Behavior public override string Trigger => "begone, @[me]"; - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { var ti = TwitchSummon.getAnyTwitchInterface(); // Console.WriteLine($"TwitchDismiss checking. menions me? {message.MentionsMe}"); diff --git a/Behavior/UnitConvert.cs b/Behavior/UnitConvert.cs index d1e2188..3826afc 100644 --- a/Behavior/UnitConvert.cs +++ b/Behavior/UnitConvert.cs @@ -15,7 +15,7 @@ public class UnitConvert : Behavior public override async Task ActOn(Message message) { - var theseMatches = Regex.Matches(message.Content, "\\s(-?[\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase); + var theseMatches = Regex.Matches(message.TranslatedContent, "\\s(-?[\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase); if (theseMatches != null && theseMatches.Count > 0 && theseMatches[0].Groups != null && theseMatches[0].Groups.Count == 5) { diff --git a/Behavior/Webhook.cs b/Behavior/Webhook.cs index f941850..42e40d7 100644 --- a/Behavior/Webhook.cs +++ b/Behavior/Webhook.cs @@ -67,9 +67,9 @@ public class Webhook : Behavior } } - public override bool ShouldAct(Message message) + public override bool ShouldAct(Message message, List matchedUACs) { - if (!base.ShouldAct(message)) + if (!base.ShouldAct(message, matchedUACs)) return false; Console.WriteLine("webhook checking"); diff --git a/ConsoleService.cs b/ConsoleService.cs index e56c40f..97adfd6 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -19,6 +19,7 @@ namespace vassago TwitchConfigs = aspConfig.GetSection("TwitchConfigs").Get>(); Conversion.Converter.Load(aspConfig["ExchangePairsLocation"]); Telefranz.Configure(aspConfig["KafkaName"], aspConfig["KafkaBootstrap"]); + Console.WriteLine($"Telefranz.Configure({aspConfig["KafkaName"]}, {aspConfig["KafkaBootstrap"]});"); vassago.Behavior.Webhook.SetupWebhooks(aspConfig.GetSection("Webhooks")); } diff --git a/Migrations/20250620023827_locales.Designer.cs b/Migrations/20250620023827_locales.Designer.cs new file mode 100644 index 0000000..29e0af6 --- /dev/null +++ b/Migrations/20250620023827_locales.Designer.cs @@ -0,0 +1,391 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using vassago.Models; + +#nullable disable + +namespace vassago.Migrations +{ + #pragma warning disable CS8981 + [DbContext(typeof(ChattingContext))] + [Migration("20250620023827_locales")] + partial class locales + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AccountUAC", b => + { + b.Property("AccountInChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("AccountInChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("AccountUAC"); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.Property("ChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("ChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("ChannelUAC"); + }); + + modelBuilder.Entity("UACUser", b => + { + b.Property("UACsId") + .HasColumnType("uuid"); + + b.Property("UsersId") + .HasColumnType("uuid"); + + b.HasKey("UACsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UACUser"); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("IsBot") + .HasColumnType("boolean"); + + b.Property("IsUserId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("SeenInChannelId") + .HasColumnType("uuid"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IsUserId"); + + b.HasIndex("SeenInChannelId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("bytea"); + + b.Property("ContentType") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("numeric(20,0)"); + + b.Property("Filename") + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("Source") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("LewdnessFilterLevel") + .HasColumnType("integer"); + + b.Property("LinksAllowed") + .HasColumnType("boolean"); + + b.Property("MaxAttachmentBytes") + .HasColumnType("numeric(20,0)"); + + b.Property("MaxTextChars") + .HasColumnType("bigint"); + + b.Property("MeannessFilterLevel") + .HasColumnType("integer"); + + b.Property("ParentChannelId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("ReactionsPossible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ParentChannelId"); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActedOn") + .HasColumnType("boolean"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("MentionsMe") + .HasColumnType("boolean"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ChannelId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("vassago.Models.UAC", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property>("CommandAlterations") + .HasColumnType("hstore"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property>("Translations") + .HasColumnType("hstore"); + + b.HasKey("Id"); + + b.ToTable("UACs"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("AccountUAC", b => + { + b.HasOne("vassago.Models.Account", null) + .WithMany() + .HasForeignKey("AccountInChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.HasOne("vassago.Models.Channel", null) + .WithMany() + .HasForeignKey("ChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("UACUser", b => + { + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.HasOne("vassago.Models.User", "IsUser") + .WithMany("Accounts") + .HasForeignKey("IsUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("vassago.Models.Channel", "SeenInChannel") + .WithMany("Users") + .HasForeignKey("SeenInChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("IsUser"); + + b.Navigation("SeenInChannel"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.HasOne("vassago.Models.Message", "Message") + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.HasOne("vassago.Models.Channel", "ParentChannel") + .WithMany("SubChannels") + .HasForeignKey("ParentChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ParentChannel"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.HasOne("vassago.Models.Account", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("vassago.Models.Channel", "Channel") + .WithMany("Messages") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Author"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Navigation("Messages"); + + b.Navigation("SubChannels"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } + #pragma warning restore CS8981 +} diff --git a/Migrations/20250620023827_locales.cs b/Migrations/20250620023827_locales.cs new file mode 100644 index 0000000..1a494e6 --- /dev/null +++ b/Migrations/20250620023827_locales.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace vassago.Migrations +{ +#pragma warning disable 8981 + /// + public partial class locales : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Localization", + table: "UACs", + newName: "Translations"); + + migrationBuilder.RenameColumn( + name: "CommandAliases", + table: "UACs", + newName: "CommandAlterations"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Translations", + table: "UACs", + newName: "Localization"); + + migrationBuilder.RenameColumn( + name: "CommandAlterations", + table: "UACs", + newName: "CommandAliases"); + } + } +#pragma warning restore 8981 +} diff --git a/Migrations/20250620230813_TranslatedMessages.Designer.cs b/Migrations/20250620230813_TranslatedMessages.Designer.cs new file mode 100644 index 0000000..ce91136 --- /dev/null +++ b/Migrations/20250620230813_TranslatedMessages.Designer.cs @@ -0,0 +1,392 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using vassago.Models; + +#nullable disable + +namespace vassago.Migrations +{ + [DbContext(typeof(ChattingContext))] + [Migration("20250620230813_TranslatedMessages")] + partial class TranslatedMessages + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AccountUAC", b => + { + b.Property("AccountInChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("AccountInChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("AccountUAC"); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.Property("ChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("ChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("ChannelUAC"); + }); + + modelBuilder.Entity("UACUser", b => + { + b.Property("UACsId") + .HasColumnType("uuid"); + + b.Property("UsersId") + .HasColumnType("uuid"); + + b.HasKey("UACsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UACUser"); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("IsBot") + .HasColumnType("boolean"); + + b.Property("IsUserId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("SeenInChannelId") + .HasColumnType("uuid"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IsUserId"); + + b.HasIndex("SeenInChannelId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("bytea"); + + b.Property("ContentType") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("numeric(20,0)"); + + b.Property("Filename") + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("Source") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("LewdnessFilterLevel") + .HasColumnType("integer"); + + b.Property("LinksAllowed") + .HasColumnType("boolean"); + + b.Property("MaxAttachmentBytes") + .HasColumnType("numeric(20,0)"); + + b.Property("MaxTextChars") + .HasColumnType("bigint"); + + b.Property("MeannessFilterLevel") + .HasColumnType("integer"); + + b.Property("ParentChannelId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("ReactionsPossible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ParentChannelId"); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActedOn") + .HasColumnType("boolean"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("MentionsMe") + .HasColumnType("boolean"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TranslatedContent") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ChannelId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("vassago.Models.UAC", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property>("CommandAlterations") + .HasColumnType("hstore"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property>("Translations") + .HasColumnType("hstore"); + + b.HasKey("Id"); + + b.ToTable("UACs"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("AccountUAC", b => + { + b.HasOne("vassago.Models.Account", null) + .WithMany() + .HasForeignKey("AccountInChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.HasOne("vassago.Models.Channel", null) + .WithMany() + .HasForeignKey("ChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("UACUser", b => + { + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.HasOne("vassago.Models.User", "IsUser") + .WithMany("Accounts") + .HasForeignKey("IsUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("vassago.Models.Channel", "SeenInChannel") + .WithMany("Users") + .HasForeignKey("SeenInChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("IsUser"); + + b.Navigation("SeenInChannel"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.HasOne("vassago.Models.Message", "Message") + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.HasOne("vassago.Models.Channel", "ParentChannel") + .WithMany("SubChannels") + .HasForeignKey("ParentChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ParentChannel"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.HasOne("vassago.Models.Account", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("vassago.Models.Channel", "Channel") + .WithMany("Messages") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Author"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Navigation("Messages"); + + b.Navigation("SubChannels"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20250620230813_TranslatedMessages.cs b/Migrations/20250620230813_TranslatedMessages.cs new file mode 100644 index 0000000..c15c4b9 --- /dev/null +++ b/Migrations/20250620230813_TranslatedMessages.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace vassago.Migrations +{ + /// + public partial class TranslatedMessages : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TranslatedContent", + table: "Messages", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TranslatedContent", + table: "Messages"); + } + } +} diff --git a/Migrations/ChattingContextModelSnapshot.cs b/Migrations/ChattingContextModelSnapshot.cs index 2eb952f..df3b658 100644 --- a/Migrations/ChattingContextModelSnapshot.cs +++ b/Migrations/ChattingContextModelSnapshot.cs @@ -218,6 +218,9 @@ namespace vassago.Migrations b.Property("Timestamp") .HasColumnType("timestamp with time zone"); + b.Property("TranslatedContent") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("AuthorId"); @@ -233,7 +236,7 @@ namespace vassago.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property>("CommandAliases") + b.Property>("CommandAlterations") .HasColumnType("hstore"); b.Property("Description") @@ -242,12 +245,12 @@ namespace vassago.Migrations b.Property("DisplayName") .HasColumnType("text"); - b.Property>("Localization") - .HasColumnType("hstore"); - b.Property("OwnerId") .HasColumnType("uuid"); + b.Property>("Translations") + .HasColumnType("hstore"); + b.HasKey("Id"); b.ToTable("UACs"); diff --git a/Models/FranzMessage.cs b/Models/FranzMessage.cs index 12a40c3..eca5bec 100644 --- a/Models/FranzMessage.cs +++ b/Models/FranzMessage.cs @@ -3,14 +3,16 @@ using System.Collections.Generic; using franz; using gray_messages; +//psst, future adam: that means we're gray_messages.chat.chat_message. namespace gray_messages.chat { public class chat_message : gray_messages.message { - //expect this to be the same every time + //expect this to be the same every time. and, "localhost". but importantly, it'll remind me of the port. public Uri Api_Uri { get; set; } public Guid MessageId { get; set; } - public string MessageContent { get; set; } + public string Content { get; set; } + public string RawContent { get; set; } public bool MentionsMe { get; set; } public DateTimeOffset Timestamp { get; set; } public uint AttachmentCount { get; set; } diff --git a/Models/Message.cs b/Models/Message.cs index d7c91e4..46c5475 100644 --- a/Models/Message.cs +++ b/Models/Message.cs @@ -15,6 +15,7 @@ public class Message public string Protocol { get; set; } public string ExternalId { get; set; } public string Content { get; set; } + public string TranslatedContent { get; set; } public bool MentionsMe { get; set; } public DateTimeOffset Timestamp { get; set; } public bool ActedOn { get; set; } diff --git a/Models/UAC.cs b/Models/UAC.cs index 8e904e9..1b0e53b 100644 --- a/Models/UAC.cs +++ b/Models/UAC.cs @@ -32,6 +32,6 @@ public class UAC /// public string Description { get; set; } - public Dictionary CommandAliases {get; set;} - public Dictionary Localization {get; set;} + public Dictionary CommandAlterations {get; set;} + public Dictionary Translations {get; set;} } diff --git a/WebInterface/Controllers/api/UACsController.cs b/WebInterface/Controllers/api/UACsController.cs index 124b4c6..7279ea2 100644 --- a/WebInterface/Controllers/api/UACsController.cs +++ b/WebInterface/Controllers/api/UACsController.cs @@ -229,4 +229,39 @@ public class UACController : ControllerBase Rememberer.RememberChannel(targetChannel); return Ok(newUAC.Id); } + [HttpPut] + [Route("AddTranslation/{Id}")] + [Produces("application/json")] + public IActionResult AddTranslation(Guid Id) + { + _logger.LogDebug($"made it to controller. creating translation for uac {Id}"); + var uacFromDb = Rememberer.SearchUAC(uac => uac.Id == Id); + if (uacFromDb == null) + { + _logger.LogError($"attempt to create translation for uac {Id}, not found"); + return NotFound(); + } + uacFromDb.Translations ??= []; + uacFromDb.Translations.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Rememberer.RememberUAC(uacFromDb); + return Ok(uacFromDb.Translations.Count); + } + [HttpPut] + [Route("AddCommandAlteration/{Id}")] + [Produces("application/json")] + public IActionResult AddCommandAlteration(Guid Id) + { + _logger.LogDebug($"made it to controller. creating command alteration for uac {Id}"); + var uacFromDb = Rememberer.SearchUAC(uac => uac.Id == Id); + if (uacFromDb == null) + { + _logger.LogError($"attempt to create command alteration for uac {Id}, not found"); + return NotFound(); + } + uacFromDb.CommandAlterations ??= []; + uacFromDb.CommandAlterations.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + Rememberer.RememberUAC(uacFromDb); + return Ok(uacFromDb.CommandAlterations.Count); + } + } diff --git a/WebInterface/Views/UACs/Details.cshtml b/WebInterface/Views/UACs/Details.cshtml index 5b7ba55..5e9e932 100644 --- a/WebInterface/Views/UACs/Details.cshtml +++ b/WebInterface/Views/UACs/Details.cshtml @@ -10,7 +10,7 @@ Display Name - @Model.DisplayName + Description @@ -19,30 +19,99 @@ Channels - @Html.Raw("
") +
Users - @Html.Raw("
") +
AccountInChannels - @Html.Raw("
") +
+ + Translations (@Model.Translations?.Count) + + next will be iterating over a dictionary. All reference on the internet implies this should work. And I'm sick of trying to figure out why it doens't. + + + @{ var i = 0; } + @foreach(var kvp in Model.Translations) + { + i++; + if(String.IsNullOrWhiteSpace(kvp.Key) || String.IsNullOrWhiteSpace(kvp.Value)) + { + continue; + } + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + } + + + + +
o_O
+ +
+ + + + Command Alterations (@Model.CommandAlterations?.Count) + + + + @{ + var j = 0; + foreach(var kvp in Model.CommandAlterations.ToList()) + { + j++; + Html.Raw(j); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + Html.Raw(""); + j++; + } + } + + + + +
o_O
+ +
+ + + + + - + + - @section Scripts{