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;
+ }
+}