From 5c69da371a187fd7b6122b00779edfa59b328e19 Mon Sep 17 00:00:00 2001 From: Madpeter <57852827+Madpeterz@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:03:20 +0100 Subject: [PATCH] add support for rabbit message Q for the bot --- Dockerfile | 13 +- SecondBotEvents/Config/RabbitConfig.cs | 72 +++++ SecondBotEvents/EventsSecondBot.cs | 2 + SecondBotEvents/SecondBotEvents.csproj | 1 + SecondBotEvents/Services/RabbitService.cs | 311 ++++++++++++++++++++++ 5 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 SecondBotEvents/Config/RabbitConfig.cs create mode 100644 SecondBotEvents/Services/RabbitService.cs diff --git a/Dockerfile b/Dockerfile index 8728342..0205e19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -152,7 +152,18 @@ ENV basic_Username='' \ smtp_User='me@email.addr.tld' \ smtp_Token='none' \ smtp_SendAlertsAndLoginsTo='me@somewhere.tld' \ - smtp_HideStatusOutput='false' + smtp_HideStatusOutput='false' \ + rabbit_Enabled='false' \ + rabbit_HideStatusOutput='false' \ + rabbit_HostIP='127.0.0.1' \ + rabbit_HostUsername='guest' \ + rabbit_HostPassword='guest' \ + rabbit_HostPort='5672' \ + rabbit_NotecardQueue='notecards' \ + rabbit_CommandQueue='commands' \ + rabbit_ImQueue='ims' \ + rabbit_GroupImQueue='groupims' \ + rabbit_LogDebug='true' EXPOSE 80 ENV ASPNETCORE_URLS http://+:80 diff --git a/SecondBotEvents/Config/RabbitConfig.cs b/SecondBotEvents/Config/RabbitConfig.cs new file mode 100644 index 0000000..1126fb5 --- /dev/null +++ b/SecondBotEvents/Config/RabbitConfig.cs @@ -0,0 +1,72 @@ +using OpenMetaverse; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class RabbitConfig(bool fromENV, string fromFolder = "") : Config(fromENV, fromFolder) + { + protected override void MakeSettings() + { + filename = "rabbit"; + settings.Add("Enabled"); + settings.Add("HideStatusOutput"); + settings.Add("HostIP"); + settings.Add("HostPort"); + settings.Add("HostUsername"); + settings.Add("HostPassword"); + settings.Add("NotecardQueue"); + settings.Add("CommandQueue"); + settings.Add("ImQueue"); + settings.Add("GroupImQueue"); + settings.Add("LogDebug"); + } + + public bool GetLogDebug() + { + return ReadSettingAsBool("LogDebug", true); + } + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", false); + } + + public string GetHostIP() + { + return ReadSettingAsString("HostIP", "127.0.0.1"); + } + + public string GetHostUsername() + { + return ReadSettingAsString("HostUsername", "guest"); + } + + public string GetHostPassword() + { + return ReadSettingAsString("HostPassword", "guest"); + } + + public int GetHostPort() + { + return ReadSettingAsInt("HostPort", 5672); + } + public string GetNotecardQueue() + { + return ReadSettingAsString("NotecardQueue", "notecards"); + } + public string GetCommandQueue() + { + return ReadSettingAsString("CommandQueue", "commands"); + } + public string GetImQueue() + { + return ReadSettingAsString("ImQueue", "ims"); + } + public string GetGroupImQueue() + { + return ReadSettingAsString("GroupImQueue", "groupims"); + } + } + +} \ No newline at end of file diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs index 5e03f2f..9983e5d 100644 --- a/SecondBotEvents/EventsSecondBot.cs +++ b/SecondBotEvents/EventsSecondBot.cs @@ -41,6 +41,7 @@ namespace SecondBotEvents public HomeboundService HomeboundService { get { return (HomeboundService)GetService("HomeboundService"); } } public EventsService EventsService { get { return (EventsService)GetService("EventsService"); } } public DialogService DialogService { get { return (DialogService)GetService("DialogService"); } } + public RabbitService RabbitService { get { return (RabbitService)GetService("RabbitService"); } } public SmtpService SmtpService { get { return (SmtpService)GetService("SmtpService"); } } @@ -177,6 +178,7 @@ namespace SecondBotEvents RegisterService("ChatGptService"); //RegisterService("RecoveryService"); RegisterService("SmtpService"); + RegisterService("RabbitService"); if (BotClient.IsLoaded() == false) { LogFormater.Info("Config is not loaded :("); diff --git a/SecondBotEvents/SecondBotEvents.csproj b/SecondBotEvents/SecondBotEvents.csproj index 5268337..36f2375 100644 --- a/SecondBotEvents/SecondBotEvents.csproj +++ b/SecondBotEvents/SecondBotEvents.csproj @@ -24,6 +24,7 @@ + diff --git a/SecondBotEvents/Services/RabbitService.cs b/SecondBotEvents/Services/RabbitService.cs new file mode 100644 index 0000000..1913019 --- /dev/null +++ b/SecondBotEvents/Services/RabbitService.cs @@ -0,0 +1,311 @@ +using SecondBotEvents.Config; +using RabbitMQ.Client; +using System; +using System.Text; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using RabbitMQ.Client.Events; +using Newtonsoft.Json; +using Swan; +using OpenMetaverse; + +namespace SecondBotEvents.Services +{ + public class RabbitService : BotServices + { + protected async Task CommandMessageEvent(object o, BasicDeliverEventArgs e) + { + try + { + JsonCommandFormat result = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(e.Body.ToArray())); + if (result == null || string.IsNullOrEmpty(result.CommandName)) + { + if (myConfig.GetLogDebug() == true) + { + LogFormater.Warn("Command Message format is invalid or missing fields. " + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + if (myConfig.GetLogDebug() == true) + { + LogFormater.Info($"Command Message UUID: {result.CommandName}, args: {String.Join(",", result.commandArgs)}"); + } + SignedCommand C = new(master.CommandsService, "rabbit", result.CommandName, "", result.commandArgs, 0, "", false, 0, "", false); + master.CommandsService.RunCommand("rabbit", C, false); + } + catch (Exception E) + { + LogFormater.Warn("Command Message decode failed " + E.Message + " raw=" + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + protected async Task NotecardMessageEvent(object o, BasicDeliverEventArgs e) + { + try + { + JsonNotecardFormat result = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(e.Body.ToArray())); + if ( + result == null || + string.IsNullOrEmpty(result.Name) || + string.IsNullOrEmpty(result.UUID) || + string.IsNullOrEmpty(result.Content) || + string.IsNullOrEmpty(result.Subject) + ) + { + if (myConfig.GetLogDebug() == true) + { + LogFormater.Warn("Notecard Message format is invalid or missing fields. " + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + if (myConfig.GetLogDebug() == true) + { + LogFormater.Info($"Notecard Message Name: {result.Name}, Subject: {result.Subject}"); + } + if (UUID.TryParse(result.UUID, out UUID uuid) == false) + { + LogFormater.Warn("Notecard Message UUID is not valid: " + result.UUID); + return Task.CompletedTask; + } + master.BotClient.SendNotecard(result.Name, result.Content, uuid, false, result.Subject); + } + catch (Exception E) + { + LogFormater.Warn("Notecard Message decode failed " + E.Message + " raw=" + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + protected async Task ImMessageEvent(object o, BasicDeliverEventArgs e) + { + Console.WriteLine("Message received: " + Encoding.UTF8.GetString(e.Body.ToArray())); + try + { + JsonMessageFormat result = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(e.Body.ToArray())); + if (result == null || string.IsNullOrEmpty(result.UUID) || string.IsNullOrEmpty(result.Message)) + { + if (myConfig.GetLogDebug() == true) + { + LogFormater.Warn("IM Message format is invalid or missing fields. " + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + if (myConfig.GetLogDebug() == true) + { + LogFormater.Info($"IM Message UUID: {result.UUID}, Message: {result.Message}"); + } + if (UUID.TryParse(result.UUID, out UUID uuid) == false) + { + LogFormater.Warn("IM Message UUID is not valid: " + result.UUID); + return Task.CompletedTask; + } + GetClient().Self.InstantMessage(uuid, result.Message); + } + catch (Exception E) + { + LogFormater.Warn("IM Message decode failed " + E.Message + " raw=" + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + protected async Task GroupMessageEvent(object o, BasicDeliverEventArgs e) + { + Console.WriteLine("Message received: " + Encoding.UTF8.GetString(e.Body.ToArray())); + try + { + JsonMessageFormat result = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(e.Body.ToArray())); + if (result == null || string.IsNullOrEmpty(result.UUID) || string.IsNullOrEmpty(result.Message)) + { + if (myConfig.GetLogDebug() == true) + { + LogFormater.Warn("Group IM Message format is invalid or missing fields. " + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + if (myConfig.GetLogDebug() == true) + { + LogFormater.Info($"Group Message UUID: {result.UUID}, Message: {result.Message}"); + } + if (UUID.TryParse(result.UUID, out UUID uuid) == false) + { + LogFormater.Warn("Group Message UUID is not valid: " + result.UUID); + return Task.CompletedTask; + } + GetClient().Self.InstantMessageGroup(uuid, result.Message); + } + catch (Exception E) + { + LogFormater.Warn("Group IM Message decode failed " + E.Message + " raw=" + Encoding.UTF8.GetString(e.Body.ToArray())); + } + return Task.CompletedTask; + } + + + public new RabbitConfig myConfig = null; + protected bool hasSecondbot = false; + public RabbitService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new RabbitConfig(master.fromEnv, master.fromFolder); + if (myConfig.GetEnabled() == false) + { + return; + } + } + ConnectionFactory factory; + IConnection connection; + Dictionary channels = new Dictionary(); + public override void Start(bool updateEnabled = false, bool setEnabledTo = false) + { + if (updateEnabled == true) + { + myConfig.setEnabled(setEnabledTo); + } + if (myConfig.GetEnabled() == false) + { + Stop(); + return; + } + LogFormater.Info("Rabbit service [Starting]"); + running = true; + master.BotClientNoticeEvent += BotClientRestart; + } + + protected void connectService() + { + if (connection != null && connection.IsOpen == true) + { + connection.CloseAsync().Await(); + connection = null; + } + var endpoints = new System.Collections.Generic.List { + new AmqpTcpEndpoint(myConfig.GetHostIP(),myConfig.GetHostPort()), + }; + ConnectionFactory factory = new ConnectionFactory(); + factory.UserName = myConfig.GetHostUsername(); + factory.Password = myConfig.GetHostPassword(); + try + { + connection = factory.CreateConnectionAsync(endpoints).Await(); + if (connection == null || connection.IsOpen == false) + { + LogFormater.Crit("Rabbit service [Failed to connect to RabbitMQ] stopping", true); + Stop(); + return; + } + + createChannelWorker(myConfig.GetNotecardQueue(), "NotecardMessageEvent"); + createChannelWorker(myConfig.GetCommandQueue(), "CommandMessageEvent"); + createChannelWorker(myConfig.GetImQueue(), "ImMessageEvent"); + createChannelWorker(myConfig.GetGroupImQueue(), "GroupMessageEvent"); + } + catch (Exception ex) + { + LogFormater.Crit("Rabbit service [Failed to connect to RabbitMQ] " + + "" + myConfig.GetHostIP() + ":" + myConfig.GetHostPort() + " " + + "with user: " + myConfig.GetHostUsername() + "\n" + + "error:" + ex.Message, true); + Stop(); + return; + } + + + } + + protected async void createChannelWorker(string queueName, string functionpointer) + { + IChannel channel = connection.CreateChannelAsync().Await(); + channels.Add(queueName, channel); + var consumer = new AsyncEventingBasicConsumer(channel); + + // Use reflection to get the method by name and create a delegate + var method = typeof(RabbitService).GetMethod(functionpointer, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (method == null) + throw new InvalidOperationException($"Handler method '{functionpointer}' not found."); + + // Create delegate matching AsyncEventHandler + AsyncEventHandler handler = + (AsyncEventHandler)Delegate.CreateDelegate( + typeof(AsyncEventHandler), this, method); + + consumer.ReceivedAsync += handler; + + string consumerTag = Guid.NewGuid().ToString(); // Generate a unique consumer tag. + channel.QueueDeclareAsync( + queue: queueName, + durable: true, + exclusive: false, + autoDelete: false, + arguments: null + ).Await(); + await channel.BasicConsumeAsync( + queue: queueName, + autoAck: true, + consumerTag: consumerTag, + noLocal: false, + exclusive: false, + arguments: null, + consumer: consumer + ); + if (myConfig.GetLogDebug() == true) + { + LogFormater.Info($"Rabbit service [Consumer started for queue: {queueName}]"); + } + } + + protected void BotClientRestart(object o, BotClientNotice e) + { + if (e.isStart == false) + { + return; + } + LogFormater.Info("Rabbit service [Attached to new client]"); + hasSecondbot = true; + connectService(); + } + + public override void Stop() + { + if (running == true) + { + running = false; + LogFormater.Info("Rabbit service [Stopping]"); + if (master != null) + { + master.BotClientNoticeEvent -= BotClientRestart; + } + } + } + + public override string Status() + { + if (myConfig == null) + { + return "No Config"; + } + else if (myConfig.GetEnabled() == false) + { + return "Disabled"; + } + return "Enabled"; + } + } + + public class JsonMessageFormat + { + public string UUID; + public string Message; + } + + public class JsonCommandFormat + { + public string CommandName; + public string[] commandArgs; + } + + public class JsonNotecardFormat + { + public string Name; + public string Subject; + public string Content; + public string UUID; + } +}