diff --git a/LibreMetaverse/GroupManager.cs b/LibreMetaverse/GroupManager.cs
index 41f56a4..fba15aa 100644
--- a/LibreMetaverse/GroupManager.cs
+++ b/LibreMetaverse/GroupManager.cs
@@ -914,7 +914,7 @@ namespace OpenMetaverse
{
if (m_GroupInvitation == null || e.IM.Dialog != InstantMessageDialog.GroupInvitation) return;
- GroupInvitationEventArgs args = new GroupInvitationEventArgs(e.Simulator, e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
+ GroupInvitationEventArgs args = new GroupInvitationEventArgs(e.Simulator, e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message, e.IM.IMSessionID);
OnGroupInvitation(args);
if (args.Accept)
@@ -2460,15 +2460,19 @@ namespace OpenMetaverse
/// The Simulator
public Simulator Simulator { get; }
+ /// The Group ID
+ public UUID GroupID { get; }
+
/// Set to true to accept invitation, false to decline
public bool Accept { get; set; }
- public GroupInvitationEventArgs(Simulator simulator, UUID agentID, string agentName, string message)
+ public GroupInvitationEventArgs(Simulator simulator, UUID agentID, string agentName, string message, UUID groupID)
{
Simulator = simulator;
AgentID = agentID;
FromName = agentName;
Message = message;
+ GroupID = groupID;
}
}
diff --git a/SecondBotEvents/Commands/Avatars.cs b/SecondBotEvents/Commands/Avatars.cs
index f47b10b..515bc65 100644
--- a/SecondBotEvents/Commands/Avatars.cs
+++ b/SecondBotEvents/Commands/Avatars.cs
@@ -78,25 +78,73 @@ namespace SecondBotEvents.Commands
return BasicReply("Error not in a sim");
}
List BetterNearMe = [];
- List avcopy = [.. GetClient().Network.CurrentSim.ObjectsAvatars.Values];
- foreach (Avatar av in avcopy)
+
+ Utils.LongToUInts(GetClient().Network.CurrentSim.Handle, out uint mySimX, out uint mySimY);
+ Vector3 myGlobalPos = new Vector3(mySimX + GetClient().Self.SimPosition.X, mySimY + GetClient().Self.SimPosition.Y, GetClient().Self.SimPosition.Z);
+
+ foreach (var sim in GetClient().Network.Simulators)
{
- if (av.ID != GetClient().Self.AgentID)
+ Utils.LongToUInts(sim.Handle, out uint simX, out uint simY);
+ HashSet processedAvatars = [];
+
+ List avcopy = [.. sim.ObjectsAvatars.Values];
+ foreach (Avatar av in avcopy)
{
- Vector3 globalPos = av.Position;
- if (av.ParentID != 0 && GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(av.ParentID, out Primitive parentObj))
+ if (av.ID == GetClient().Self.AgentID) continue;
+
+ processedAvatars.Add(av.ID);
+
+ Vector3 localPos = av.Position;
+ if (av.ParentID != 0 && sim.ObjectsPrimitives.TryGetValue(av.ParentID, out Primitive parentObj))
{
- globalPos = parentObj.Position + (av.Position * parentObj.Rotation);
+ localPos = parentObj.Position + (av.Position * parentObj.Rotation);
}
+ Vector3 globalPos = new Vector3(simX + localPos.X, simY + localPos.Y, localPos.Z);
+ double dist = Math.Round(Vector3.Distance(globalPos, myGlobalPos), 2);
+
+ if (dist > 150) continue;
+
+ float relX = (simX - mySimX) + localPos.X;
+ float relY = (simY - mySimY) + localPos.Y;
+
NearMeDetails details = new()
{
id = av.ID.ToString(),
name = av.Name,
- x = (int)Math.Round(globalPos.X),
- y = (int)Math.Round(globalPos.Y),
+ x = (int)Math.Round(relX),
+ y = (int)Math.Round(relY),
z = (int)Math.Round(globalPos.Z),
- range = Math.Round(Vector3.Distance(globalPos, GetClient().Self.SimPosition), 2)
+ range = dist
+ };
+ BetterNearMe.Add(details);
+ }
+
+ foreach (var kvp in sim.AvatarPositions)
+ {
+ UUID avID = kvp.Key;
+ if (avID == GetClient().Self.AgentID || processedAvatars.Contains(avID)) continue;
+
+ Vector3 localPos = kvp.Value;
+ Vector3 globalPos = new Vector3(simX + localPos.X, simY + localPos.Y, localPos.Z);
+ double dist = Math.Round(Vector3.Distance(globalPos, myGlobalPos), 2);
+
+ if (dist > 150) continue;
+
+ float relX = (simX - mySimX) + localPos.X;
+ float relY = (simY - mySimY) + localPos.Y;
+
+ string avName = master.DataStoreService.GetAvatarName(avID);
+ if (avName == "lookup" || string.IsNullOrEmpty(avName)) avName = "Unknown";
+
+ NearMeDetails details = new()
+ {
+ id = avID.ToString(),
+ name = avName,
+ x = (int)Math.Round(relX),
+ y = (int)Math.Round(relY),
+ z = (int)Math.Round(globalPos.Z),
+ range = dist
};
BetterNearMe.Add(details);
}
diff --git a/SecondBotEvents/Commands/Chat.cs b/SecondBotEvents/Commands/Chat.cs
index 0741df5..16c06d1 100644
--- a/SecondBotEvents/Commands/Chat.cs
+++ b/SecondBotEvents/Commands/Chat.cs
@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
using OpenMetaverse;
using SecondBotEvents.Services;
@@ -31,7 +31,8 @@ namespace SecondBotEvents.Commands
{
return Failure("Invaild channel", [channel, message]);
}
- master.DataStoreService.BotRecordLocalchatReply(message);
+ string typeTag = (channelnum == 0) ? "" : $"[Channel {channelnum}] ";
+ master.DataStoreService.BotRecordLocalchatReply(message, typeTag);
GetClient().Self.Chat(message, channelnum, ChatType.Normal);
return BasicReply("ok");
}
@@ -52,7 +53,7 @@ namespace SecondBotEvents.Commands
{
return Failure("Invaild channel", [channel, message]);
}
- master.DataStoreService.BotRecordLocalchatReply(message);
+ master.DataStoreService.BotRecordLocalchatReply(message, "[Shout] ");
GetClient().Self.Chat(message, channelnum, ChatType.Shout);
return BasicReply("ok");
}
@@ -73,7 +74,7 @@ namespace SecondBotEvents.Commands
{
return Failure("Invaild channel", [channel, message]);
}
- master.DataStoreService.BotRecordLocalchatReply(message);
+ master.DataStoreService.BotRecordLocalchatReply(message, "[Whisper] ");
GetClient().Self.Chat(message, channelnum, ChatType.Whisper);
return BasicReply("ok");
}
diff --git a/SecondBotEvents/Commands/Group.cs b/SecondBotEvents/Commands/Group.cs
index c3da88d..85cec3f 100644
--- a/SecondBotEvents/Commands/Group.cs
+++ b/SecondBotEvents/Commands/Group.cs
@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
using OpenMetaverse;
using SecondBotEvents.Services;
using System.Collections.Generic;
@@ -444,5 +444,31 @@ namespace SecondBotEvents.Commands
master.DataStoreService.BotRecordReplyIM(groupUUID, message);
return BasicReply("Sending");
}
+ [About("fetchs a list of all pending group invitations")]
+ [ReturnHints("array of GroupInvite objects")]
+ [CmdTypeGet()]
+ public object GetPendingGroupInvites()
+ {
+ return BasicReply(JsonSerializer.Serialize(master.DataStoreService.GetPendingGroupInvites(), JsonOptions.UnsafeRelaxed));
+ }
+
+ [About("Responds to a group invitation")]
+ [ArgHints("group", "the group UUID to respond to", "UUID")]
+ [ArgHints("accept", "true to join, false to decline", "BOOL")]
+ [CmdTypeDo()]
+ public object GroupInviteRespond(string group, string accept)
+ {
+ if (UUID.TryParse(group, out UUID groupuuid) == false)
+ {
+ return Failure("Invaild group UUID", [group, accept]);
+ }
+ bool doAccept = accept.ToLower() == "true";
+ if (doAccept == true)
+ {
+ GetClient().Groups.RequestJoinGroup(groupuuid);
+ }
+ master.DataStoreService.RemovePendingGroupInvite(groupuuid);
+ return BasicReply("Done");
+ }
}
}
diff --git a/SecondBotEvents/Commands/Info.cs b/SecondBotEvents/Commands/Info.cs
index 86159f9..56821dc 100644
--- a/SecondBotEvents/Commands/Info.cs
+++ b/SecondBotEvents/Commands/Info.cs
@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
using OpenMetaverse;
using SecondBotEvents.Services;
using System;
@@ -160,6 +160,19 @@ namespace SecondBotEvents.Commands
};
return BasicReply(JsonSerializer.Serialize(pos, JsonOptions.UnsafeRelaxed));
}
+ [About("Fetchs the current RLV restrictions applied to the bot")]
+ [ReturnHints("A JSON array of RLV rules")]
+ [CmdTypeGet()]
+ public object RLVStatus()
+ {
+ if (master.RLV == null)
+ {
+ return Failure("RLV service is not running");
+ }
+ var rules = master.RLV.Rules;
+ if (rules == null) return BasicReply("[]");
+ return BasicReply(JsonSerializer.Serialize(rules, JsonOptions.UnsafeRelaxed));
+ }
}
public class SculptysInfo
diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs
index b88920b..8082bf9 100644
--- a/SecondBotEvents/EventsSecondBot.cs
+++ b/SecondBotEvents/EventsSecondBot.cs
@@ -1,4 +1,4 @@
-using SecondBotEvents.Services;
+using SecondBotEvents.Services;
using System;
using System.Linq;
using System.Reflection;
@@ -16,6 +16,12 @@ namespace SecondBotEvents
#endif
LogFormater.Info("Welcome to Secondbot [Events build] version: " + AssemblyInfo.GetGitHash());
EventsSecondBot worker = new(args);
+
+ AppDomain.CurrentDomain.ProcessExit += (s, e) => {
+ LogFormater.Warn("SIGTERM received, initiating graceful shutdown...");
+ worker.StopServices();
+ Thread.Sleep(2000);
+ };
while (worker.Exit() == false)
{
diff --git a/SecondBotEvents/Services/BotClientService.cs b/SecondBotEvents/Services/BotClientService.cs
index c0be957..26ec6ef 100644
--- a/SecondBotEvents/Services/BotClientService.cs
+++ b/SecondBotEvents/Services/BotClientService.cs
@@ -1,4 +1,4 @@
-using OpenMetaverse;
+using OpenMetaverse;
using OpenMetaverse.Assets;
using SecondBotEvents.Config;
using System;
@@ -80,6 +80,13 @@ namespace SecondBotEvents.Services
AutoRestartLoginTimer.Stop();
AutoRestartLoginTimer = null;
}
+ if (client != null && client.Network != null && client.Network.Connected)
+ {
+ LogFormater.Warn("Service stopping - initiating logout");
+ flagLogoutExpected();
+ client.Network.Logout();
+ Thread.Sleep(3000); // Wait for packets to send
+ }
ResetClient();
}
diff --git a/SecondBotEvents/Services/DataStoreService.cs b/SecondBotEvents/Services/DataStoreService.cs
index f0a7046..71d8157 100644
--- a/SecondBotEvents/Services/DataStoreService.cs
+++ b/SecondBotEvents/Services/DataStoreService.cs
@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
using OpenMetaverse;
using SecondBotEvents.Config;
using System;
@@ -15,6 +15,16 @@ using Swan;
namespace SecondBotEvents.Services
{
+ public class GroupInvite
+ {
+ public UUID GroupID { get; set; }
+ public string GroupName { get; set; }
+ public UUID AgentID { get; set; }
+ public string AgentName { get; set; }
+ public UUID RoleID { get; set; }
+ public string Message { get; set; }
+ }
+
public class DataStoreService : BotServices
{
protected new DataStoreConfig myConfig;
@@ -46,6 +56,7 @@ namespace SecondBotEvents.Services
protected Dictionary KeyValueStoreLastUsed = [];
protected List commandHistories = [];
+ protected List PendingGroupInvites = [];
@@ -469,11 +480,11 @@ namespace SecondBotEvents.Services
{
RecordChat(sessionid, "Bot", message);
}
- public void BotRecordLocalchatReply(string message)
+ public void BotRecordLocalchatReply(string message, string typeTag = "")
{
lock (localChatHistory)
{
- localChatHistory.Add("{BOT} " + GetClient().Self.Name + ": " + message);
+ localChatHistory.Add("{BOT} " + typeTag + GetClient().Self.Name + ": " + message);
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
{
return;
@@ -599,6 +610,42 @@ namespace SecondBotEvents.Services
return KnownAvatar(name, UUID.Zero);
}
+ public void AddPendingGroupInvite(UUID groupID, UUID agentID, string agentName, string message)
+ {
+ lock (PendingGroupInvites)
+ {
+ // Avoid duplicates
+ if (PendingGroupInvites.Any(i => i.GroupID == groupID))
+ {
+ return;
+ }
+ PendingGroupInvites.Add(new GroupInvite
+ {
+ GroupID = groupID,
+ GroupName = "Unknown Group", // Will be updated on lookup
+ AgentID = agentID,
+ AgentName = agentName,
+ Message = message
+ });
+ }
+ }
+
+ public List GetPendingGroupInvites()
+ {
+ lock (PendingGroupInvites)
+ {
+ return [.. PendingGroupInvites];
+ }
+ }
+
+ public void RemovePendingGroupInvite(UUID groupID)
+ {
+ lock (PendingGroupInvites)
+ {
+ PendingGroupInvites.RemoveAll(i => i.GroupID == groupID);
+ }
+ }
+
protected int ObjectsAvatarsHash = 0;
/*
@@ -1135,6 +1182,8 @@ namespace SecondBotEvents.Services
case ChatType.Whisper:
case ChatType.Normal:
case ChatType.Shout:
+ case ChatType.OwnerSay:
+ case ChatType.RegionSayTo:
{
if (hard_blocked_agents.Contains(e.FromName.ToLowerInvariant()) == true)
{
@@ -1144,7 +1193,11 @@ namespace SecondBotEvents.Services
{
break;
}
- string source = "{Av}";
+ if (string.IsNullOrWhiteSpace(e.Message))
+ {
+ break;
+ }
+ string source = "{Av}";
if (e.SourceType == ChatSourceType.Object)
{
source = "{Obj}";
@@ -1153,6 +1206,13 @@ namespace SecondBotEvents.Services
{
source = "{Sys}";
}
+
+ string typeTag = "";
+ if (e.Type == ChatType.Shout) typeTag = "[Shout] ";
+ else if (e.Type == ChatType.Whisper) typeTag = "[Whisper] ";
+ else if (e.Type == ChatType.OwnerSay) typeTag = "[OwnerSay] ";
+ else if (e.Type == ChatType.RegionSayTo) typeTag = "[RegionSayTo] ";
+
lock (localChatHistory)
{
if (source == "{Av}")
@@ -1160,7 +1220,7 @@ namespace SecondBotEvents.Services
AddAvatar(e.SourceID, e.FromName);
}
- localChatHistory.Add(source+ " "+e.FromName + ": " + e.Message);
+ localChatHistory.Add(source + " " + typeTag + e.FromName + ": " + e.Message);
if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit())
{
break;
diff --git a/SecondBotEvents/Services/InteractionService.cs b/SecondBotEvents/Services/InteractionService.cs
index a4ae5c0..a72cea1 100644
--- a/SecondBotEvents/Services/InteractionService.cs
+++ b/SecondBotEvents/Services/InteractionService.cs
@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
using OpenMetaverse;
using SecondBotEvents.Config;
using System;
@@ -233,10 +233,13 @@ namespace SecondBotEvents.Services
Dictionary reply = new()
{
{ "message", e.Message },
- { "fromuuid", e.AgentID.ToString() }
+ { "fromuuid", e.AgentID.ToString() },
+ { "groupuuid", e.GroupID.ToString() }
};
if (ProcessRequest(myConfig.GetAcceptGroupInvites(),myConfig.GetGroupInviteLevel(), e.FromName, "GroupInvite", e.AgentID, reply) == false)
{
+ // Not auto-accepted, record for dashboard
+ master.DataStoreService.AddPendingGroupInvite(e.GroupID, e.AgentID, e.FromName, e.Message);
return;
}
e.Accept = true;
diff --git a/SecondBotEvents/Services/RLVService.cs b/SecondBotEvents/Services/RLVService.cs
index 0f2fab4..b406468 100644
--- a/SecondBotEvents/Services/RLVService.cs
+++ b/SecondBotEvents/Services/RLVService.cs
@@ -1,4 +1,4 @@
-using Discord;
+using Discord;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using Org.BouncyCastle.Asn1.Ocsp;
@@ -242,6 +242,7 @@ namespace SecondBotEvents.Services
EventsSecondBot instance = null;
readonly Regex rlv_regex = new(@"(?[^:=]+)(:(?