using System.Text.Json; using OpenMetaverse; using SecondBotEvents.Config; using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; namespace SecondBotEvents.Services { public class InteractionService : BotServices { protected new InteractionConfig myConfig; protected bool botConnected = false; private readonly Dictionary _pendingOffers = []; public UUID GetOrCreatePendingFolder() { var client = GetClient(); if (client == null || client.Inventory == null || client.Inventory.Store == null || client.Inventory.Store.RootFolder == null) { return UUID.Zero; } var store = client.Inventory.Store; var rootContent = store.GetContents(store.RootFolder.UUID); foreach (var baseItem in rootContent) { if (baseItem is InventoryFolder folder && folder.Name.Equals("#Pending Approval", StringComparison.OrdinalIgnoreCase)) { return folder.UUID; } } return client.Inventory.CreateFolder(store.RootFolder.UUID, "#Pending Approval"); } public InteractionService(EventsSecondBot setMaster) : base(setMaster) { myConfig = new InteractionConfig(master.fromEnv,master.fromFolder); if (myConfig.GetEnabled() == false) { LogFormater.Info(Status()); return; } } public override string Status() { if (myConfig == null) { return "No Config"; } else if (myConfig.GetHideStatusOutput() == true) { return "hidden"; } else if (myConfig.GetEnabled() == false) { return "- Not requested -"; } else if (botConnected == false) { return "Waiting for bot"; } return "Active"; } protected bool ProcessRequest(bool enabledByConfig, string ConfigAcceptLevel, string avatarName, string sourceName, UUID avataruuid, Dictionary misc = null) { if (misc == null) { misc = []; } if (myConfig.GetEnabled() == false) { JsonOuput(false, sourceName, avatarName, "Interactions hooks disabled"); return false; } if(enabledByConfig == false) { JsonOuput(false, sourceName, avatarName, "disabled by config"); return false; } if (master.CommandsService.myConfig.GetEnableMasterControls() == false) { // master avatars list is not enabled. unable to continue JsonOuput(false, sourceName, avatarName, "masters AV list is disabled: Please set EnableMasterControls in Commands service to true"); return false; } bool accepted = false; string ObjectOwnerName = master.DataStoreService.GetAvatarName(avataruuid); string whyAccepted = "rejected request"; misc.Add("ObjectOwner", ObjectOwnerName); foreach (string masterName in master.CommandsService.myConfig.GetMastersCSV()) { if (SecondbotHelpers.IsNameMatch(masterName, avatarName)) { whyAccepted = "on owners list"; accepted = true; break; } if (SecondbotHelpers.IsNameMatch(masterName, ObjectOwnerName)) { whyAccepted = "Object Owner is on master list"; accepted = true; break; } } if (ConfigAcceptLevel == "Anyone") { whyAccepted = "Accepting from anyone"; accepted = true; } else if((ConfigAcceptLevel == "Friends") && (sourceName != "FriendRequest")) { whyAccepted = "on friends list"; accepted = GetClient().Friends.FriendList.ContainsKey(avataruuid); } if ((accepted == false) && (master.CommandsService.myConfig.GetCheckDotNames() == true)) { string[] bits = avatarName.ToLower().Split('.'); if (bits.Length == 2) { string dotName = bits[0].FirstCharToUpper() + " " + bits[1].FirstCharToUpper(); foreach (string masterName in master.CommandsService.myConfig.GetMastersCSV()) { if (SecondbotHelpers.IsNameMatch(masterName, dotName)) { accepted = true; whyAccepted = "on owners list [dot check]"; break; } } } if (accepted == false) { bits = ObjectOwnerName.ToLower().Split('.'); if (bits.Length == 2) { string dotName = bits[0].FirstCharToUpper() + " " + bits[1].FirstCharToUpper(); foreach (string masterName in master.CommandsService.myConfig.GetMastersCSV()) { if (SecondbotHelpers.IsNameMatch(masterName, dotName)) { accepted = true; whyAccepted = "Object Owner is on master list [dot check]"; break; } } } } } if (accepted == false) { // check the one time accept list accepted = master.DataStoreService.GetNextAccept(sourceName, avataruuid); if(accepted == true) { whyAccepted = "was on NextAccept list"; } } JsonOuput(accepted, sourceName, avatarName, whyAccepted, misc); return accepted; } protected void BotClientRestart(object o, BotClientNotice e) { if (e.isStart == false) { return; } botConnected = false; LogFormater.Info("Interaction Service [Attached to new client]"); GetClient().Network.LoggedOut += BotLoggedOut; GetClient().Network.SimConnected += BotLoggedIn; } protected void JsonOuput(bool status, string eventype, string from, string why="", Dictionary setMisc = null) { if(myConfig.GetEnableDebug() == true) { LogFormater.Warn("Interaction debug:" + JsonSerializer.Serialize(new InteractionEvent(from, eventype, status, why, setMisc), JsonOptions.UnsafeRelaxed)); } if (myConfig.GetEnableJsonOutputEvents() == false) { return; } master.CommandsService.SmartCommandReply( myConfig.GetJsonOutputEventsTarget(), JsonSerializer.Serialize(new InteractionEvent(from, eventype, status, why, setMisc), JsonOptions.UnsafeRelaxed), "interactions" ); } protected void JsonOuputCleaner(string misc, string source) { Dictionary reply = new() { { "value", misc } }; JsonOuputCleaner(reply, source); } protected void JsonOuputCleaner(Dictionary misc, string source) { if (myConfig.GetEnableDebug() == true) { LogFormater.Warn("Interaction debug:" + JsonSerializer.Serialize(misc, JsonOptions.UnsafeRelaxed)); } if (myConfig.GetEnableJsonOutputEvents() == false) { return; } misc.Add("eventsource", source); master.CommandsService.SmartCommandReply( myConfig.GetJsonOutputEventsTarget(), JsonSerializer.Serialize(misc, JsonOptions.UnsafeRelaxed), "interactions" ); } protected void BotTeleportOffer(object o, InstantMessageEventArgs e) { if((e.IM.Dialog != InstantMessageDialog.RequestTeleport) && (e.IM.Dialog != InstantMessageDialog.RequestLure)) { return; } string mode = "Teleport Bot to Av"; bool markTeleport = true; if (e.IM.Dialog == InstantMessageDialog.RequestLure) { mode = "Teleport Av to Bot"; markTeleport = false; } Dictionary misc = new() { { "mode", mode } }; if (ProcessRequest(myConfig.GetAcceptTeleports(),myConfig.GetTeleportRequestLevel(), e.IM.FromAgentName, "Teleport", e.IM.FromAgentID, misc) == false) { // rejected requests if (e.IM.Dialog != InstantMessageDialog.RequestLure) { return; } GetClient().Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, false); return; } if (markTeleport == true) { master.HomeboundService.MarkTeleport(); } JsonOuputCleaner(e.IM.FromAgentName, mode); if (e.IM.Dialog == InstantMessageDialog.RequestLure) { GetClient().Self.SendTeleportLure(e.IM.FromAgentID); return; } GetClient().Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true); } protected void BotGroupInviteOffer(object o, GroupInvitationEventArgs e) { Dictionary reply = new() { { "message", e.Message }, { "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; JsonOuputCleaner(reply, "GroupInvite"); } protected void BotInventoryAdd(object o, InventoryObjectAddedEventArgs e) { InventoryItem F = (InventoryItem)e.Obj; UUID pendingFolder = GetOrCreatePendingFolder(); if (F.ParentUUID == pendingFolder) { (string SenderName, UUID SenderID) senderDetails = ("Unknown", UUID.Zero); bool found = false; lock (_pendingOffers) { if (_pendingOffers.TryGetValue(F.UUID, out senderDetails)) { found = true; _pendingOffers.Remove(F.UUID); } } if (found) { F.Description = $"Sent by: {senderDetails.SenderName} ({senderDetails.SenderID})"; GetClient().Inventory.RequestUpdateItem(F); } } Dictionary details = new() { { "itemname", e.Obj.Name }, { "itemuuid", e.Obj.UUID.ToString() }, { "itemtype", F.AssetType.ToString() }, }; if (master.EventsService.isRunning() == true) { master.EventsService.InventoryUpdateEvent(details); } JsonOuputCleaner(details, "InventoryAdd"); } protected void BotInventoryOffer(object o, InventoryObjectOfferedEventArgs e) { UUID ownerID = e.Offer.FromAgentID; string ownerName = e.Offer.FromAgentName; if (e.FromTask) { var sim = GetClient().Network.CurrentSim; var prim = sim?.ObjectsPrimitives.Values.FirstOrDefault(p => p.ID == e.Offer.FromAgentID); if (prim != null) { ownerID = prim.OwnerID; } else { System.Threading.AutoResetEvent propertiesEvent = new(false); EventHandler handler = (sender, args) => { if (args.Properties.ObjectID == e.Offer.FromAgentID) { ownerID = args.Properties.OwnerID; propertiesEvent.Set(); } }; GetClient().Objects.ObjectPropertiesFamily += handler; GetClient().Objects.RequestObjectPropertiesFamily(GetClient().Network.CurrentSim, e.Offer.FromAgentID); propertiesEvent.WaitOne(1000); GetClient().Objects.ObjectPropertiesFamily -= handler; } ownerName = master.DataStoreService.GetAvatarName(ownerID); } Dictionary details = new() { { "transactionid", e.Offer.IMSessionID.ToString() }, { "itemtype", e.AssetType.ToString() }, { "message", e.Offer.Message.ToString() }, { "fromscript", e.FromTask.ToString() }, { "targetfolder", e.FolderID.ToString() }, { "objectid", e.Offer.FromAgentID.ToString() }, { "objectname", e.Offer.FromAgentName } }; if (ProcessRequest(myConfig.GetAcceptInventory(),myConfig.GetInventoryTransferLevel(), ownerName, "InventoryOffer", ownerID, details) == false) { if (myConfig.GetAcceptInventory()) { UUID pendingFolder = GetOrCreatePendingFolder(); e.FolderID = pendingFolder; e.Accept = true; lock (_pendingOffers) { _pendingOffers[e.ObjectID] = (ownerName, ownerID); } details.Add("pending", "true"); details.Add("itemuuid", e.ObjectID.ToString()); details.Add("itemname", "?"); JsonOuputCleaner(details, "InventoryOffer"); } else { e.Accept = false; } return; } e.Accept = true; if(e.FromTask == false) { master.DataStoreService.AddAvatar(e.Offer.FromAgentID, e.Offer.FromAgentName); details.Add("itemuuid", e.ObjectID.ToString()); details.Add("itemname", "?"); InventoryItem itm = GetClient().Inventory.FetchItem(e.ObjectID, GetClient().Self.AgentID, TimeSpan.FromSeconds(15)); if(itm != null) { details["itemname"] = itm.Name; } } JsonOuputCleaner(details, "InventoryOffer"); } protected void BotFriendRequested(object o, FriendshipOfferedEventArgs e) { Dictionary misc = new() { { "fromName", e.AgentName } }; if (ProcessRequest(myConfig.GetAcceptFriendRequests(),myConfig.GetFriendRequestLevel(), e.AgentName, "FriendRequest", e.AgentID, misc) == false) { return; } master.DataStoreService.AddAvatar(e.AgentID, e.AgentName); GetClient().Friends.AcceptFriendship(e.AgentID, e.SessionID); JsonOuputCleaner(e.AgentName, "FriendRequest"); } protected void BotLoggedOut(object o, LoggedOutEventArgs e) { botConnected = false; GetClient().Network.SimConnected += BotLoggedIn; LogFormater.Info("Interaction Service [Standby]"); } async Task awaitstable() { await Task.Delay(3000); } protected async void BotLoggedIn(object o, SimConnectedEventArgs e) { await awaitstable(); GetOrCreatePendingFolder(); GetClient().Network.SimConnected -= BotLoggedIn; GetClient().Friends.FriendshipOffered += BotFriendRequested; GetClient().Inventory.InventoryObjectOffered += BotInventoryOffer; GetClient().Groups.GroupInvitation += BotGroupInviteOffer; GetClient().Inventory.Store.InventoryObjectAdded += BotInventoryAdd; GetClient().Self.IM += BotTeleportOffer; botConnected = true; LogFormater.Info("Interaction Service [Active]"); } public override void Start(bool updateEnabled = false, bool setEnabledTo = false) { if (updateEnabled) { myConfig.setEnabled(setEnabledTo); } if (myConfig.GetEnabled() == false) { LogFormater.Info("Interaction Service [- Not requested -]"); return; } Stop(); running = true; master.BotClientNoticeEvent += BotClientRestart; LogFormater.Info("Interaction Service [Starting]"); } public override void Stop() { if(running == true) { LogFormater.Info("Interaction Service [Stopping]"); } running = false; if (myConfig.GetEnabled() == false) { return; } master.BotClientNoticeEvent -= BotClientRestart; if (master.BotClient != null) { var client = GetClient(); if (client != null) { if (client.Friends != null) client.Friends.FriendshipOffered -= BotFriendRequested; if (client.Inventory != null) { client.Inventory.InventoryObjectOffered -= BotInventoryOffer; if (client.Inventory.Store != null) client.Inventory.Store.InventoryObjectAdded -= BotInventoryAdd; } if (client.Groups != null) client.Groups.GroupInvitation -= BotGroupInviteOffer; } } } } public class InteractionEvent { public string from = "?"; public string eventType = "?"; public bool accepted = false; public string info = ""; public Dictionary misc = []; public InteractionEvent(string setFrom, string setType, bool setAccepted, string setInfo, Dictionary setMisc = null) { accepted = setAccepted; from = setFrom; eventType = setType; info = setInfo; if(setMisc != null) { misc = setMisc; } } } }