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(@"(?[^:=]+)(:(?