using System.Text.Json; using OpenMetaverse; using SecondBotEvents.Config; using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Threading; using SecondBotEvents.Commands; using OpenMetaverse.ImportExport.Collada14; using System.Threading.Tasks; using OpenMetaverse.Messages.Linden; using Org.BouncyCastle.Asn1.BC; 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 FriendRequest { public UUID AgentID { get; set; } public string AgentName { get; set; } public UUID SessionID { get; set; } public string Message { get; set; } } public class DataStoreService : BotServices { protected new DataStoreConfig myConfig; protected bool botConnected = false; private static readonly Random _delayRandom = new(); public DataStoreService(EventsSecondBot setMaster) : base(setMaster) { myConfig = new DataStoreConfig(master.fromEnv,master.fromFolder); LoadPendingGroupInvites(); } protected Dictionary avatarsName2Key = []; protected Dictionary avatarLastUsed = []; protected Dictionary avatarsKey2Displayname = []; protected Dictionary groupsKey2Name = []; protected Dictionary> groupMembers = []; protected Dictionary> groupRoles = []; protected Dictionary> loginTokens = []; // Avatar -> {Token, Expiry} protected List localChatHistory = []; protected Dictionary> chatWindows = []; // sess, list chat protected Dictionary chatWindowsUnread = []; // sess, bool protected Dictionary chatWindowsIsGroup = []; // sess, bool protected Dictionary chatWindowsOwner = []; // sess, owner id protected Dictionary KeyValueStore = []; protected Dictionary KeyValueStoreLastUsed = []; protected List commandHistories = []; protected List PendingGroupInvites = []; protected List PendingFriendRequests = []; protected long lastCleanupKeyValueStore = 0; protected long lastCleanupAvatarStore = 0; protected List acceptedStores = ["FriendRequest", "InventoryOffer", "GroupInvite", "Teleport"]; protected Dictionary> AcceptNextStore = []; public bool KnownStoreType(string storeType) { return acceptedStores.Contains(storeType); } public bool GetNextAccept(string store,UUID avatar) { if(AcceptNextStore.ContainsKey(store) == false) { return false; } else if (AcceptNextStore[store].Contains(avatar) == false) { return false; } AcceptNextStore[store].Remove(avatar); return true; } public bool IsInAcceptNext(string store,UUID avatar) { if (AcceptNextStore.ContainsKey(store) == false) { return false; } return AcceptNextStore[store].Contains(avatar); } public bool AddToAcceptNext(string store,UUID avatar, bool remove) { if(acceptedStores.Contains(store) == false) { return false; } if(AcceptNextStore.ContainsKey(store) == false) { AcceptNextStore.Add(store, []); } if(AcceptNextStore[store].Contains(avatar) == true) { if(remove == true) { AcceptNextStore[store].Remove(avatar); return true; } return true; // already in the store } if(remove == true) { return true; // already removed from the store } AcceptNextStore[store].Add(avatar); return true; } public bool GetIsGroup(UUID MaybeGroupUUID) { return groupsKey2Name.ContainsKey(MaybeGroupUUID); } public string GetGroupName(UUID group) { if (groupsKey2Name.ContainsKey(group) == true) { return groupsKey2Name[group]; } string reply = "lookup"; AutoResetEvent fetchEvent = new(false); void callback(object sender, CurrentGroupsEventArgs e) { foreach(KeyValuePair entry in e.Groups) { if(entry.Value.ID == group) { reply = entry.Value.Name; fetchEvent.Set(); break; } } } GetClient().Groups.CurrentGroups += callback; GetClient().Groups.RequestCurrentGroups(); fetchEvent.WaitOne(1000, false); GetClient().Groups.CurrentGroups -= callback; return reply; } public override string Status() { if (myConfig == null) { return "DataStore Service [Config broken]"; } else if (myConfig.GetHideStatusOutput() == true) { return "hidden"; } long unixtime = SecondbotHelpers.UnixTimeNow(); if (myConfig.GetAutoCleanKeyValueStore() == true) { if (lastCleanupKeyValueStore == 0) { lastCleanupKeyValueStore = SecondbotHelpers.UnixTimeNow(); } long dif = unixtime - lastCleanupKeyValueStore; if (dif > 125) { lastCleanupKeyValueStore = unixtime; List clearentrys = []; foreach (KeyValuePair entry in KeyValueStoreLastUsed) { dif = unixtime - entry.Value; if (dif < (myConfig.GetCleanKeyValueStoreAfterMins()*60)) { continue; } clearentrys.Add(entry.Key); } lock (KeyValueStore) { foreach (string A in clearentrys) { KeyValueStore.Remove(A); KeyValueStoreLastUsed.Remove(A); } } } } if(myConfig.GetAutoCleanAvatars() == true) { if(lastCleanupAvatarStore == 0) { lastCleanupAvatarStore = unixtime; } long dif = unixtime - lastCleanupAvatarStore; if (dif > 75) { lastCleanupAvatarStore = unixtime; Dictionary clearLinks = []; foreach (KeyValuePair entry in avatarsName2Key) { dif = unixtime - avatarLastUsed[entry.Value]; if (dif < (myConfig.GetAvatarsCleanAfterMins() * 60)) { continue; } clearLinks.Add(entry.Value, entry.Key); } lock (avatarLastUsed) lock (avatarsName2Key) lock(avatarsKey2Displayname) { foreach (KeyValuePair A in clearLinks) { avatarLastUsed.Remove(A.Key); avatarsName2Key.Remove(A.Value); avatarsKey2Displayname.Remove(A.Key); } } } } if (myConfig.GetPrefetchInventory() == true) { if(preloadDone == false) { if(preloadingFolders.Count() == 0) { preloadDone = true; } return "Loading "+preloadingFolders.Count()+" inventory folders"; } } int sum = commandHistories.Count + KeyValueStoreLastUsed.Count + KeyValueStore.Count + chatWindowsOwner.Count + chatWindowsIsGroup.Count + chatWindowsUnread.Count + chatWindows.Count + localChatHistory.Count + groupRoles.Count + groupMembers.Count + groupsKey2Name.Count + avatarsName2Key.Count + avprops.Count; string returndat = "Total entrys: "+sum.ToString()+", Preloaded inventory: "+preloadFolderCount.ToString()+" folders and "+preloadItemCount.ToString()+" items"; return returndat; } public void AddCommandToHistory(bool status, string command, string[] args, string results=null) { lock (commandHistories) { CommandHistory A = new(status, command, args, SecondbotHelpers.UnixTimeNow(), results); commandHistories.Add(A); int c = commandHistories.Count; while (c > myConfig.GetCommandHistoryLimit()) { commandHistories.RemoveAt(0); c--; } } } public List GetCommandHistory() { return commandHistories; } public string GetKeyValue(string key) { lock (KeyValueStore) { if (KeyValueStore.ContainsKey(key) == false) { return ""; } KeyValueStoreLastUsed[key] = SecondbotHelpers.UnixTimeNow(); return KeyValueStore[key]; } } public void SetKeyValue(string key, string value) { lock (KeyValueStore) { if (KeyValueStore.ContainsKey(key) == false) { KeyValueStore.Add(key, ""); KeyValueStoreLastUsed.Add(key, 0); } KeyValueStore[key] = value; KeyValueStoreLastUsed[key] = SecondbotHelpers.UnixTimeNow(); } } public void ClearKeyValue(string key) { lock (KeyValueStore) { if (KeyValueStore.ContainsKey(key) == false) { return; } KeyValueStore.Remove(key); KeyValueStoreLastUsed.Remove(key); } } public void AppendKeyValue(string key, string value) { if (KeyValueStore.ContainsKey(key) == false) { SetKeyValue(key, value); return; } KeyValueStoreLastUsed[key] = SecondbotHelpers.UnixTimeNow(); KeyValueStore[key] = KeyValueStore[key] + value; } public Dictionary GetGroups() { return groupsKey2Name; } public List GetAllGroupsChatWithUnread() { List reply = []; foreach(KeyValuePair windowState in chatWindowsUnread) { if(windowState.Value == false) { continue; } if(chatWindowsIsGroup[windowState.Key] == false) { continue; } reply.Add(windowState.Key); } return reply; } public bool GetGroupChatHasUnread(UUID group) { if(chatWindowsUnread.ContainsKey(group) == false) { return false; } return chatWindowsUnread[group]; } public bool GetGroupChatHasAnyUnread() { bool reply = false; foreach(KeyValuePair check in chatWindowsIsGroup) { if(check.Value == false) { continue; } if(chatWindowsUnread[check.Key] == true) { reply = true; break; } } return reply; } public List GetGroupChatHistory(UUID group) { if(chatWindows.ContainsKey(group) == false) { return []; } chatWindowsUnread[group] = false; return chatWindows[group]; } public void ClearGroupChat() { lock (chatWindows) { foreach (UUID group in groupsKey2Name.Keys) { if (chatWindowsUnread.ContainsKey(group) == false) { continue; } chatWindowsUnread.Remove(group); chatWindows.Remove(group); chatWindowsIsGroup.Remove(group); chatWindowsOwner.Remove(group); } } } public string GetGroupMemberRank(UUID group, UUID avatar) { lock (groupMembers) { if (groupMembers.ContainsKey(group) == false || groupMembers[group].ContainsKey(avatar) == false) { LogFormater.Info($"[DEBUG] GetGroupMemberRank: Returning Guest. GroupKnown: {groupMembers.ContainsKey(group)}, AvatarKnown: {(groupMembers.ContainsKey(group) ? groupMembers[group].ContainsKey(avatar).ToString() : "N/A")} for Group: {group}, Avatar: {avatar}"); return "Guest"; } GroupMember m = groupMembers[group][avatar]; if (m.IsOwner == true) return "Owner"; if (m.Title != null && (m.Title.Contains("Officer") || m.Title.Contains("Officer"))) return "Officer"; // Also check roles if title didn't match if (groupRoles.ContainsKey(group)) { // This is a bit complex as we need member-to-role mapping which we don't have cached yet // For now, Owner and Title check is a good start. } return "Member"; } } public string RequestLoginToken(UUID avatar) { string token = _delayRandom.Next(100000, 999999).ToString(); long expiry = SecondbotHelpers.UnixTimeNow() + 300; // 5 mins lock (loginTokens) { loginTokens[avatar] = new KeyValuePair(token, expiry); } return token; } public bool VerifyLoginToken(UUID avatar, string token) { lock (loginTokens) { if (loginTokens.ContainsKey(avatar) == false) return false; if (loginTokens[avatar].Value < SecondbotHelpers.UnixTimeNow()) { loginTokens.Remove(avatar); return false; } if (loginTokens[avatar].Key == token) { loginTokens.Remove(avatar); return true; } return false; } } public Dictionary GetGroupRoles(UUID group, bool force = false) { Dictionary reply = []; if (force || groupRoles.ContainsKey(group) == false) { AutoResetEvent fetchEvent = new(false); void callback(object sender, GroupRolesDataReplyEventArgs e) { if (e.GroupID == group) { foreach (KeyValuePair role in e.Roles) { reply[role.Value.ID] = role.Value.Name; } fetchEvent.Set(); } } GetClient().Groups.GroupRoleDataReply += callback; GetClient().Groups.RequestGroupRoles(group); fetchEvent.WaitOne(1000, false); GetClient().Groups.GroupRoleDataReply -= callback; return reply; } foreach (KeyValuePair role in groupRoles[group]) { reply[role.Value.ID] = role.Value.Name; } return reply; } public bool IsGroupMember(UUID group, UUID avatar) { bool reply = true; lock (groupMembers) { if (groupMembers.ContainsKey(group) == false) { groupMembers.Add(group, new Dictionary()); } if (groupMembers[group].ContainsKey(avatar) == false) { reply = false; AutoResetEvent fetchEvent = new(false); void callback(object sender, GroupMembersReplyEventArgs e) { if (e.GroupID == group) { foreach (KeyValuePair a in e.Members) { if (a.Value.ID == avatar) { reply = true; fetchEvent.Set(); break; } } } } GetClient().Groups.GroupMembersReply += callback; GetClient().Groups.RequestGroupMembers(group); fetchEvent.WaitOne(1000, false); GetClient().Groups.GroupMembersReply -= callback; } } return reply; } public List GetGroupMembers(UUID Group) { lock (groupMembers) { if (groupMembers.ContainsKey(Group) == false) { groupMembers.Add(Group, new Dictionary()); } return groupMembers[Group].Keys.ToList(); } } public void BotRecordReplyIM(UUID sessionid,string message) { RecordChat(sessionid, "{BOT} " + GetClient().Self.Name, message); } public void BotRecordLocalchatReply(string message, string typeTag = "") { lock (localChatHistory) { long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); localChatHistory.Add("[" + ms + "] {BOT} " + typeTag + GetClient().Self.Name + ": " + message); if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit()) { return; } localChatHistory.RemoveAt(0); } } protected void RecordChat(UUID sessionID, string fromname, string message) { lock (chatWindows) { if (chatWindows.ContainsKey(sessionID) == false) { return; // chat window not open unable to record message } long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); chatWindows[sessionID].Add("[" + ms + "] " + fromname + ": " + message); chatWindowsUnread[sessionID] = true; int purgeLen = myConfig.GetImChatHistoryLimit(); if (chatWindowsIsGroup[sessionID] == true) { purgeLen = myConfig.GetGroupChatHistoryLimitPerGroup(); } int counter = chatWindows[sessionID].Count; while (counter > purgeLen) { chatWindows[sessionID].RemoveAt(0); counter--; } } } public void CloseChat(UUID owner) { lock (chatWindows) { UUID sessionID = UUID.Zero; if (chatWindowsOwner.ContainsValue(owner) == false) { return; } foreach (KeyValuePair ownermap in chatWindowsOwner) { if (ownermap.Value != owner) { continue; } sessionID = ownermap.Key; } if (sessionID == UUID.Zero) { return; } chatWindowsOwner.Remove(sessionID); chatWindowsIsGroup.Remove(sessionID); chatWindowsUnread.Remove(sessionID); chatWindows.Remove(sessionID); } } public void OpenChatWindow(bool group, UUID sessionID, UUID ownerID) { lock (chatWindows) { if (chatWindows.ContainsKey(sessionID) == true) { return; // window already open } if (chatWindowsOwner.ContainsValue(ownerID) == true) { // new session find the old one and update to this session Dictionary oldOwners = chatWindowsOwner; foreach (KeyValuePair kvp in oldOwners) { if (kvp.Value == ownerID) { chatWindows.Add(sessionID, chatWindows[kvp.Key]); chatWindowsUnread.Add(sessionID, chatWindowsUnread[kvp.Key]); chatWindowsIsGroup.Add(sessionID, chatWindowsIsGroup[kvp.Key]); chatWindowsOwner.Add(sessionID, ownerID); chatWindows.Remove(kvp.Key); chatWindowsUnread.Remove(kvp.Key); chatWindowsIsGroup.Remove(kvp.Key); chatWindowsOwner.Remove(kvp.Key); break; } } return; // chat recovered } chatWindows.Add(sessionID, []); chatWindowsUnread.Add(sessionID, false); chatWindowsIsGroup.Add(sessionID, group); chatWindowsOwner.Add(sessionID, ownerID); // new window now open } } protected Dictionary> notecardDataStore = []; public List GetLocalChat() { return localChatHistory; } public bool KnownGroup(UUID groupId, string name) { if(groupId != UUID.Zero) { return groupsKey2Name.ContainsKey(groupId); } return groupsKey2Name.ContainsValue(name); } /* * checks if the avatar is known to the system * if not the bot will attempt to * lookup the avatar */ public bool KnownAvatar(string name) { return KnownAvatar(name, UUID.Zero); } private static readonly string groupInvitesFilePath = "data/pending_group_invites.json"; private void SavePendingGroupInvites() { try { string dir = System.IO.Path.GetDirectoryName(groupInvitesFilePath); if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir)) { System.IO.Directory.CreateDirectory(dir); } string json = JsonSerializer.Serialize(PendingGroupInvites, JsonOptions.UnsafeRelaxed); System.IO.File.WriteAllText(groupInvitesFilePath, json); } catch (Exception ex) { LogFormater.Crit("Failed to save pending group invites: " + ex.Message); } } private void LoadPendingGroupInvites() { try { if (System.IO.File.Exists(groupInvitesFilePath)) { string json = System.IO.File.ReadAllText(groupInvitesFilePath); var loaded = JsonSerializer.Deserialize>(json); if (loaded != null) { lock (PendingGroupInvites) { PendingGroupInvites = loaded; } } } } catch (Exception ex) { LogFormater.Crit("Failed to load pending group invites: " + ex.Message); } } public void AddPendingGroupInvite(UUID groupID, UUID agentID, string agentName, string message) { lock (PendingGroupInvites) { // Avoid duplicates if (PendingGroupInvites.Any(i => i.GroupID == groupID)) { return; } string groupName = "Unknown Group"; if (GetClient().Groups.GroupName2KeyCache.TryGetValue(groupID, out string resolvedCacheName)) { groupName = resolvedCacheName; } else if (groupsKey2Name.TryGetValue(groupID, out string cachedName)) { groupName = cachedName; } else { AutoResetEvent fetchEvent = new(false); void callback(object sender, GroupNamesEventArgs e) { if (e.GroupNames.TryGetValue(groupID, out string resolvedName)) { groupName = resolvedName; fetchEvent.Set(); } } GetClient().Groups.GroupNamesReply += callback; GetClient().Groups.RequestGroupName(groupID); fetchEvent.WaitOne(1500, false); GetClient().Groups.GroupNamesReply -= callback; if (groupName != "Unknown Group") { lock (groupsKey2Name) { groupsKey2Name[groupID] = groupName; } } } PendingGroupInvites.Add(new GroupInvite { GroupID = groupID, GroupName = groupName, AgentID = agentID, AgentName = agentName, Message = message }); SavePendingGroupInvites(); } } public List GetPendingGroupInvites() { lock (PendingGroupInvites) { return [.. PendingGroupInvites]; } } public void RemovePendingGroupInvite(UUID groupID) { lock (PendingGroupInvites) { PendingGroupInvites.RemoveAll(i => i.GroupID == groupID); SavePendingGroupInvites(); } } public void AddPendingFriendRequest(UUID agentID, string agentName, UUID sessionID, string message) { lock (PendingFriendRequests) { // Avoid duplicates if (PendingFriendRequests.Any(r => r.AgentID == agentID)) { return; } PendingFriendRequests.Add(new FriendRequest { AgentID = agentID, AgentName = agentName, SessionID = sessionID, Message = message }); } } public List GetPendingFriendRequests() { lock (PendingFriendRequests) { return [.. PendingFriendRequests]; } } public void RemovePendingFriendRequest(UUID agentID) { lock (PendingFriendRequests) { PendingFriendRequests.RemoveAll(r => r.AgentID == agentID); } } protected int ObjectsAvatarsHash = 0; /* * checks if the avatar is known to the system * if not the bot will attempt to * lookup the avatar */ protected void SyncAvatarsHashMap() { int currentHash = GetClient().Network.CurrentSim.ObjectsAvatars.GetHashCode(); if (currentHash != ObjectsAvatarsHash) { ObjectsAvatarsHash = currentHash; List avs = [.. GetClient().Network.CurrentSim.ObjectsAvatars.ToDictionary(k => k.Key, v => v.Value).Values]; foreach (Avatar A in avs) { AddAvatar(A.ID, A.Name); } } } protected bool KnownAvatarUUID(UUID avatarId) { if (avatarsName2Key.ContainsValue(avatarId) == true) { return true; } bool hasReply = false; AutoResetEvent fetchEvent = new(false); void callback(object sender, UUIDNameReplyEventArgs e) { foreach (KeyValuePair av in e.Names) { if (av.Key == avatarId) { hasReply = true; fetchEvent.Set(); } } } GetClient().Avatars.UUIDNameReply += callback; GetClient().Avatars.RequestAvatarName(avatarId); fetchEvent.WaitOne(1000, false); GetClient().Avatars.UUIDNameReply -= callback; return hasReply; } protected bool KnownAvatarName(string name) { if (name == null) { return false; } string[] bits = name.Split(' '); if (bits.Length == 1) { name += " Resident"; } if (avatarsName2Key.ContainsKey(name) == true) { return true; } UUID wantedReplyID = UUID.SecureRandom(); bool hasReply = false; AutoResetEvent fetchEvent = new(false); void callback(object sender, AvatarPickerReplyEventArgs e) { if (e.QueryID == wantedReplyID) { foreach (KeyValuePair av in e.Avatars) { if (av.Value == name) { hasReply = true; fetchEvent.Set(); } } } } GetClient().Avatars.AvatarPickerReply += callback; GetClient().Avatars.RequestAvatarNameSearch(name, wantedReplyID); fetchEvent.WaitOne(1000, false); GetClient().Avatars.AvatarPickerReply -= callback; return hasReply; } public bool KnownAvatar(string name, UUID avatarId) { SyncAvatarsHashMap(); if (avatarId == UUID.Zero) { return false; } if(KnownAvatarUUID(avatarId) == true) { return true; } if(KnownAvatarName(name) == true) { return true; } return false; } public void GetAvatarNames(List avatars) { List lookup = []; foreach (UUID id in avatars) { if (avatarsName2Key.ContainsValue(id) == false) { lookup.Add(id); } } if(lookup.Count == 0) { return; } GetClient().Avatars.RequestAvatarNames(lookup); } public string GetAvatarName(UUID avatarId) { string reply = "lookup"; lock (avatarLastUsed) { if (KnownAvatar(null, avatarId) == false) { return reply; } foreach (KeyValuePair av in avatarsName2Key) { if (av.Value == avatarId) { avatarLastUsed[av.Value] = SecondbotHelpers.UnixTimeNow(); reply = av.Key; break; } } } return reply; } public string GetAvatarUUID(string name) { if (KnownAvatar(name) == false) { return "lookup"; } lock (avatarLastUsed) { avatarLastUsed[avatarsName2Key[name]] = SecondbotHelpers.UnixTimeNow(); } return avatarsName2Key[name].ToString(); } public Dictionary GetAvatarImWindows() { Dictionary windows = []; foreach (KeyValuePair links in chatWindowsIsGroup) { if(links.Value == true) { continue; } if (chatWindowsIsGroup[links.Key] == true) { continue; } windows[chatWindowsOwner[links.Key]] = GetAvatarName(chatWindowsOwner[links.Key]); } return windows; } public List GetAvatarImWindow(UUID avatarId) { if (chatWindowsOwner.ContainsValue(avatarId) == false) { return []; } UUID sessionID = UUID.Zero; foreach (KeyValuePair links in chatWindowsOwner) { if (links.Value == avatarId) { sessionID = links.Key; break; } } if(chatWindowsIsGroup[sessionID] == true) { return []; } chatWindowsUnread[sessionID] = false; return chatWindows[sessionID]; } public bool GetAvatarImWindowsUnreadAny() { bool replyState = false; foreach (KeyValuePair links in chatWindowsIsGroup) { if (links.Value == true) { continue; } if (chatWindowsIsGroup[links.Key] == true) { continue; } if (chatWindowsUnread[links.Key] == false) { continue; } replyState = true; break; } return replyState; } public List GetAvatarImWindowsUnread() { List windows = []; foreach (KeyValuePair links in chatWindowsIsGroup) { if (links.Value == true) { continue; } if (chatWindowsIsGroup[links.Key] == true) { continue; } if (chatWindowsUnread[links.Key] == false) { continue; } windows.Add(chatWindowsOwner[links.Key]); } return windows; } protected void Reset() { if (estateBanlist != null) { lock (avatarsName2Key) lock (groupMembers) lock (localChatHistory) lock (chatWindows) lock (estateBanlist) { ClearMem(); } return; } ClearMem(); } protected void ClearMem() { lastCleanupKeyValueStore = 0; avatarsName2Key = []; groupsKey2Name = []; groupMembers = []; groupRoles = []; localChatHistory = []; notecardDataStore = []; chatWindows = []; // sess, list chat chatWindowsUnread = []; // sess, bool chatWindowsIsGroup = []; // sess, bool chatWindowsOwner = []; // sess, owner id estateBanlist = []; } protected void BotClientRestart(object o, BotClientNotice e) { if (e.isStart == false) { return; } botConnected = false; LogFormater.Info("DataStore Service [Attached to new client]"); GetClient().Network.LoggedOut += BotLoggedOut; GetClient().Network.SimConnected += BotLoggedIn; } protected void BotLoggedOut(object o, LoggedOutEventArgs e) { botConnected = false; GetClient().Network.SimConnected += BotLoggedIn; LogFormater.Info("DataStore Service [Standby]"); } protected void GroupRoles(object o, GroupRolesDataReplyEventArgs e) { lock (groupRoles) { if (groupRoles.ContainsKey(e.GroupID) == false) { groupRoles.Add(e.GroupID, []); } groupRoles[e.GroupID] = e.Roles; } } protected void GroupMembers(object o, GroupMembersReplyEventArgs e) { lock (groupMembers) { if (groupMembers.ContainsKey(e.GroupID) == false) { groupMembers.Add(e.GroupID, new Dictionary()); } else { groupMembers[e.GroupID].Clear(); } foreach (KeyValuePair a in e.Members) { groupMembers[e.GroupID][a.Key] = a.Value; } } } public List GetEstateBans() { if (estateBanlist == null) { List reply = []; AutoResetEvent fetchEvent = new(false); void callback(object sender, EstateBansReplyEventArgs e) { reply = e.Banned; fetchEvent.Set(); } GetClient().Estate.EstateBansReply += callback; GetClient().Estate.RequestInfo(); fetchEvent.WaitOne(1000, false); GetClient().Estate.EstateBansReply -= callback; return reply; } return estateBanlist; } protected List estateBanlist = null; protected Dictionary FriendMapLocations = []; public FriendLocations GetFriendMap(UUID id) { if(FriendMapLocations.ContainsKey(id) == false) { return null; } return FriendMapLocations[id]; } protected void FindFriendReply(object o, FriendFoundReplyEventArgs e) { Utils.LongToUInts(e.RegionHandle, out uint x, out uint y); x /= 256; y /= 256; Dictionary FriendListCopy = GetClient().Friends.FriendList.ToDictionary(k => k.Key, v => v.Value); if (FriendListCopy.ContainsKey(e.AgentID) == false) { return; } FriendLocations NewEntry = new(FriendListCopy[e.AgentID].Name, e.Location, new Vector3(x,y,10)); if(FriendMapLocations.ContainsKey(e.AgentID) == false) { FriendMapLocations.Add(e.AgentID, NewEntry); } FriendMapLocations[e.AgentID] = NewEntry; } protected void EstateBans(object o, EstateBansReplyEventArgs e) { lock (estateBanlist) { estateBanlist = e.Banned; } } protected void BotLoggedIn(object o, SimConnectedEventArgs e) { GetClient().Network.SimConnected -= BotLoggedIn; botConnected = true; LogFormater.Info("DataStore Service [Attaching]"); GetAvatarName(GetClient().Self.AgentID); Task.Factory.StartNew(() => { Thread.Sleep(2000); AttachEventsAfterDelay(); }); } protected string activeGroupTitle = ""; protected Dictionary avprops = []; protected Dictionary> avpicks = []; protected Dictionary> avclassifieds = []; protected Dictionary> avpicksDetails = []; protected Dictionary> avclassifiedsDetails = []; protected Dictionary> avgroups = []; public void ClearAvatarPicksCache(UUID agentID) { avpicks.Remove(agentID); lock (avpicksDetails) { avpicksDetails.Remove(agentID); } } public void ClearAvatarClassifiedsCache(UUID agentID) { avclassifieds.Remove(agentID); lock (avclassifiedsDetails) { avclassifiedsDetails.Remove(agentID); } } public KeyValuePair> GetAvatarPicks(UUID agentID) { if (!avpicks.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarPicks(agentID); return new KeyValuePair>(false, []); } return new KeyValuePair>(true, avpicks[agentID]); } public KeyValuePair> GetAvatarPicksDetailed(UUID agentID) { if (!avpicks.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarPicks(agentID); return new KeyValuePair>(false, []); } lock (avpicksDetails) { if (!avpicksDetails.ContainsKey(agentID)) { avpicksDetails.Add(agentID, []); foreach (UUID key in avpicks[agentID].Keys) { GetClient().Avatars.RequestPickInfo(agentID, key); } } else { foreach (UUID pickID in new List(avpicksDetails[agentID].Keys)) { ProfilePick value = avpicksDetails[agentID][pickID]; if (!string.IsNullOrEmpty(value.SimName) || !(value.PosGlobal.X > 0.0) || !(value.PosGlobal.Y > 0.0)) { continue; } float localX; float localY; ulong handle = Helpers.GlobalPosToRegionHandle((float)value.PosGlobal.X, (float)value.PosGlobal.Y, out localX, out localY); if (GetClient().Grid.RegionsByHandle.TryGetValue(handle, out var value2)) { value.SimName = value2.Name; avpicksDetails[agentID][pickID] = value; continue; } Task.Run(delegate { if (GetClient().Grid.GetGridRegion(handle, GridLayerType.Terrain, out var region)) { lock (avpicksDetails) { if (avpicksDetails.ContainsKey(agentID) && avpicksDetails[agentID].ContainsKey(pickID)) { ProfilePick value3 = avpicksDetails[agentID][pickID]; value3.SimName = region.Name; avpicksDetails[agentID][pickID] = value3; } } } }); } } return new KeyValuePair>(true, avpicksDetails[agentID]); } } protected void AvatarPicksReply(object sender, AvatarPicksReplyEventArgs e) { lock (avpicks) { if (!avpicks.ContainsKey(e.AvatarID)) { avpicks.Add(e.AvatarID, []); } avpicks[e.AvatarID] = e.Picks; lock (avpicksDetails) { if (!avpicksDetails.ContainsKey(e.AvatarID)) { avpicksDetails.Add(e.AvatarID, []); } else { List list = []; foreach (UUID key in avpicksDetails[e.AvatarID].Keys) { if (!e.Picks.ContainsKey(key)) { list.Add(key); } } foreach (UUID item in list) { avpicksDetails[e.AvatarID].Remove(item); } } foreach (UUID key2 in e.Picks.Keys) { GetClient().Avatars.RequestPickInfo(e.AvatarID, key2); } } } } protected void PickInfoReply(object sender, PickInfoReplyEventArgs e) { lock (avpicksDetails) { UUID avatarID = e.Pick.CreatorID; if (avatarID == UUID.Zero) { lock (avpicks) { foreach (KeyValuePair> avpick in avpicks) { if (avpick.Value.ContainsKey(e.PickID)) { avatarID = avpick.Key; break; } } } } if (avatarID == UUID.Zero) { return; } if (!avpicksDetails.ContainsKey(avatarID)) { avpicksDetails.Add(avatarID, []); } ProfilePick pick = e.Pick; if (string.IsNullOrEmpty(pick.SimName) && pick.PosGlobal.X > 0.0 && pick.PosGlobal.Y > 0.0) { float localX; float localY; ulong handle = Helpers.GlobalPosToRegionHandle((float)pick.PosGlobal.X, (float)pick.PosGlobal.Y, out localX, out localY); if (GetClient().Grid.RegionsByHandle.TryGetValue(handle, out var value)) { pick.SimName = value.Name; } else { Task.Run(delegate { if (GetClient().Grid.GetGridRegion(handle, GridLayerType.Terrain, out var region)) { lock (avpicksDetails) { if (avpicksDetails.ContainsKey(avatarID) && avpicksDetails[avatarID].ContainsKey(e.PickID)) { ProfilePick value2 = avpicksDetails[avatarID][e.PickID]; value2.SimName = region.Name; avpicksDetails[avatarID][e.PickID] = value2; } } } }); } } avpicksDetails[avatarID][e.PickID] = pick; } } public KeyValuePair> GetAvatarClassifieds(UUID agentID) { if (!avclassifieds.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarClassified(agentID); return new KeyValuePair>(false, []); } return new KeyValuePair>(true, avclassifieds[agentID]); } public KeyValuePair> GetAvatarClassifiedsDetailed(UUID agentID) { if (!avclassifieds.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarClassified(agentID); return new KeyValuePair>(false, []); } lock (avclassifiedsDetails) { if (!avclassifiedsDetails.ContainsKey(agentID)) { avclassifiedsDetails.Add(agentID, []); foreach (UUID key in avclassifieds[agentID].Keys) { GetClient().Avatars.RequestClassifiedInfo(agentID, key); } } return new KeyValuePair>(true, avclassifiedsDetails[agentID]); } } protected void AvatarClassifiedsReply(object sender, AvatarClassifiedReplyEventArgs e) { lock (avclassifieds) { if (!avclassifieds.ContainsKey(e.AvatarID)) { avclassifieds.Add(e.AvatarID, []); } avclassifieds[e.AvatarID] = e.Classifieds; lock (avclassifiedsDetails) { if (!avclassifiedsDetails.ContainsKey(e.AvatarID)) { avclassifiedsDetails.Add(e.AvatarID, []); } else { List list = []; foreach (UUID key in avclassifiedsDetails[e.AvatarID].Keys) { if (!e.Classifieds.ContainsKey(key)) { list.Add(key); } } foreach (UUID item in list) { avclassifiedsDetails[e.AvatarID].Remove(item); } } foreach (UUID key2 in e.Classifieds.Keys) { GetClient().Avatars.RequestClassifiedInfo(e.AvatarID, key2); } } } } protected void ClassifiedInfoReply(object sender, ClassifiedInfoReplyEventArgs e) { lock (avclassifiedsDetails) { UUID uUID = UUID.Zero; lock (avclassifieds) { foreach (KeyValuePair> avclassified in avclassifieds) { if (avclassified.Value.ContainsKey(e.ClassifiedID)) { uUID = avclassified.Key; break; } } } if (uUID != UUID.Zero) { if (!avclassifiedsDetails.ContainsKey(uUID)) { avclassifiedsDetails.Add(uUID, []); } avclassifiedsDetails[uUID][e.ClassifiedID] = e.Classified; } } } public KeyValuePair GetAvatarAvatarProperties(UUID agentID) { if (!avprops.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarProperties(agentID); return new KeyValuePair(false, new Avatar.AvatarProperties()); } return new KeyValuePair(true, avprops[agentID]); } public void UpdateAvatarProperties(UUID agentID, Avatar.AvatarProperties properties) { lock (avprops) { avprops[agentID] = properties; } } protected void AvatarDetailsReply(object sender, AvatarPropertiesReplyEventArgs e) { if (!avprops.ContainsKey(e.AvatarID)) { avprops.Add(e.AvatarID, new Avatar.AvatarProperties()); } avprops[e.AvatarID] = e.Properties; } public KeyValuePair> GetAvatarGroups(UUID agentID) { if (!avgroups.ContainsKey(agentID)) { GetClient().Avatars.RequestAvatarProperties(agentID); return new KeyValuePair>(false, []); } return new KeyValuePair>(true, avgroups[agentID]); } protected void AvatarGroupsReplyCallback(object sender, AvatarGroupsReplyEventArgs e) { lock (avgroups) { avgroups[e.AvatarID] = e.Groups; } } public string GetActiveGroupTitle() { return activeGroupTitle; } public void SetActiveGroupTitle(string title) { activeGroupTitle = title; } protected void AttachEventsAfterDelay() { GetClient().Groups.CurrentGroups += GroupCurrent; GetClient().Groups.GroupJoinedReply += GroupJoin; GetClient().Groups.GroupLeaveReply += GroupLeave; GetClient().Avatars.UUIDNameReply += AvatarUUIDName; GetClient().Friends.FriendNames += AvatarFriendNames; GetClient().Self.IM += ImEvent; GetClient().Self.ChatFromSimulator += ChatFromSim; GetClient().Avatars.AvatarPickerReply += AvatarUUIDNamePicker; GetClient().Groups.GroupMembersReply += GroupMembers; GetClient().Groups.GroupRoleDataReply += GroupRoles; GetClient().Estate.EstateBansReply += EstateBans; GetClient().Friends.FriendFoundReply += FindFriendReply; GetClient().Avatars.AvatarPropertiesReply += AvatarDetailsReply; GetClient().Avatars.AvatarGroupsReply += AvatarGroupsReplyCallback; GetClient().Avatars.AvatarPicksReply += AvatarPicksReply; GetClient().Avatars.AvatarClassifiedReply += AvatarClassifiedsReply; GetClient().Avatars.PickInfoReply += PickInfoReply; GetClient().Avatars.ClassifiedInfoReply += ClassifiedInfoReply; GetClient().Groups.RequestCurrentGroups(); _ = GetClient().Self.RetrieveInstantMessages(); if(myConfig.GetPrefetchInventory() == true) { preloadDone = false; _ = Task.Run(async () => { await preloadFolder("root", GetClient().Inventory.Store.RootFolder.UUID, myConfig.GetPrefetchInventoryDepth(), 0); }); } } protected List preloadingFolders = []; protected bool preloadDone = true; protected int preloadFolderCount = 0; protected int preloadItemCount = 0; public int GetPreloadedFolderCount() { return preloadFolderCount; } public int GetPreloadedItemCount() { return preloadItemCount; } public bool preloadInventoryReady() { if(myConfig.GetPrefetchInventory() == false) { return true; } else if(preloadFolderCount == 0) { return false; } else if(preloadingFolders.Count() > 0) { return false; } return true; } protected async Task preloadFolder(string foldername,UUID folder, int maxDepth, int currentDepth) { lock (preloadingFolders) { preloadingFolders.Add(folder.Guid.ToString()); } await Task.Delay(_delayRandom.Next(500, 1251) * (currentDepth+1)); List foldercontents = GetClient().Inventory.FolderContents( folder, GetClient().Self.AgentID, true, true, InventorySortOrder.ByDate, TimeSpan.FromSeconds(30), false); List subFolderTasks = []; foreach (InventoryBase item in foldercontents) { string entrytype = item.GetType().Name.Replace("Inventory", ""); if (entrytype != "Folder") { Interlocked.Increment(ref preloadItemCount); continue; } if ((currentDepth + 1) > maxDepth) { continue; } // Start loading subfolder but don't wait for it yet subFolderTasks.Add(preloadFolder(item.Name, item.UUID, maxDepth, currentDepth + 1)); } // Wait for all subfolder tasks to complete if (subFolderTasks.Count > 0) { await Task.WhenAll(subFolderTasks); } lock (preloadingFolders) { preloadFolderCount++; preloadingFolders.Remove(folder.Guid.ToString()); } } readonly string[] hard_blocked_agents = ["secondlife", "second life"]; protected void ChatFromSim(object o, ChatEventArgs e) { switch(e.Type) { case ChatType.Whisper: case ChatType.Normal: case ChatType.Shout: case ChatType.OwnerSay: case ChatType.RegionSayTo: { if (hard_blocked_agents.Contains(e.FromName.ToLowerInvariant()) == true) { break; } /* if (e.SourceID == GetClient().Self.AgentID) { break; } */ if (string.IsNullOrWhiteSpace(e.Message)) { break; } string source = "{Av}"; if (e.SourceID == GetClient().Self.AgentID) { source = "{BOT}"; } else if (e.SourceType == ChatSourceType.Object) { source = "{Obj}"; } else if (e.SourceType == ChatSourceType.System) { 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}") { AddAvatar(e.SourceID, e.FromName); } long ms = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); localChatHistory.Add("[" + ms + "] " + source + " " + typeTag + e.FromName + ": " + e.Message); if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit()) { break; } localChatHistory.RemoveAt(0); } break; } default: { break; } } } protected void GroupCurrent(object o, CurrentGroupsEventArgs e) { lock (groupsKey2Name) { List missingGroups = [.. groupsKey2Name.Keys]; foreach (Group G in e.Groups.Values) { missingGroups.Remove(G.ID); if (groupsKey2Name.ContainsKey(G.ID) == true) { continue; } groupsKey2Name.Add(G.ID, G.Name); GetClient().Self.RequestJoinGroupChat(G.ID); if (myConfig.GetPrefetchGroupMembers() == true) { GetClient().Groups.RequestGroupMembers(G.ID); } if (myConfig.GetPrefetchGroupRoles() == true) { GetClient().Groups.RequestGroupRoles(G.ID); } } foreach (UUID G in missingGroups) { if (groupsKey2Name.ContainsKey(G) == false) { continue; } groupsKey2Name.Remove(G); CloseChat(G); } } } protected void GroupJoin(object o, GroupOperationEventArgs e) { GetClient().Groups.RequestCurrentGroups(); } protected void GroupLeave(object o, GroupOperationEventArgs e) { GetClient().Groups.RequestCurrentGroups(); } protected void displayNameFinder(UUID Avatar) { if (myConfig.GetPrefetchAvatarDisplaynames() == true) { requestDisplayName(Avatar); } } protected void requestDisplayName(UUID Avatar) { if (avatarsKey2Displayname.ContainsKey(Avatar) == false) { return; // unable to fetch not in avatar DB } if (avatarsKey2Displayname[Avatar] != "?") { return; // already known } _ = GetClient().Avatars.RequestAgentProfile(Avatar, (status, properties) => { avatarsKey2Displayname[Avatar] = properties.DisplayName; }); } public void AddAvatar(UUID id, string name) { lock (avatarsName2Key) lock (avatarLastUsed) lock(avatarsKey2Displayname) { if (avatarsName2Key.ContainsValue(id) == true) { avatarLastUsed[id] = SecondbotHelpers.UnixTimeNow(); displayNameFinder(id); return; } avatarsName2Key.Add(name, id); avatarLastUsed.Add(id, SecondbotHelpers.UnixTimeNow()); avatarsKey2Displayname.Add(id, "?"); displayNameFinder(id); } } public string GetDisplayName(UUID id) { requestDisplayName(id); if (avatarsKey2Displayname.ContainsKey(id) == false) { return "?"; } return avatarsKey2Displayname[id]; } protected void AvatarUUIDName(object o, UUIDNameReplyEventArgs e) { foreach(KeyValuePair pair in e.Names) { AddAvatar(pair.Key, pair.Value); } } protected void AvatarUUIDNamePicker(object o, AvatarPickerReplyEventArgs e) { foreach(KeyValuePair Av in e.Avatars) { AddAvatar(Av.Key, Av.Value); } } protected void AvatarFriendNames(object o, FriendNamesEventArgs e) { foreach (KeyValuePair pair in e.Names) { AddAvatar(pair.Key, pair.Value); } } protected void ImEvent(object o, InstantMessageEventArgs e) { switch(e.IM.Dialog) { case InstantMessageDialog.GroupNotice: { if(groupsKey2Name.ContainsKey(e.IM.IMSessionID) == false) { break; } OpenChatWindow(true, e.IM.IMSessionID, e.IM.IMSessionID); RecordChat(e.IM.IMSessionID, "**Notice**", e.IM.Message); break; } case InstantMessageDialog.SessionSend: case InstantMessageDialog.MessageFromAgent: { AddAvatar(e.IM.FromAgentID, e.IM.FromAgentName); if (groupsKey2Name.ContainsKey(e.IM.IMSessionID) == true) { // group IM OpenChatWindow(true, e.IM.IMSessionID, e.IM.IMSessionID); if (e.IM.FromAgentID == GetClient().Self.AgentID) { RecordChat(e.IM.IMSessionID, "{BOT} " + GetClient().Self.Name, e.IM.Message); } else { RecordChat(e.IM.IMSessionID, e.IM.FromAgentName, e.IM.Message); } break; } UUID SessionID = e.IM.IMSessionID; string message = e.IM.Message; if (e.IM.Offline == InstantMessageOnline.Offline) { SessionID = UUID.Random(); message = "{AV offline} " + message; } OpenChatWindow(false, SessionID, e.IM.FromAgentID); if (e.IM.FromAgentID != GetClient().Self.AgentID) { RecordChat(SessionID, e.IM.FromAgentName, e.IM.Message); } else { RecordChat(SessionID, "{BOT} " + GetClient().Self.Name, e.IM.Message); } break; } case InstantMessageDialog.FriendshipOffered: { AddAvatar(e.IM.FromAgentID, e.IM.FromAgentName); AddPendingFriendRequest(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.IMSessionID, e.IM.Message); break; } default: { break; } } } public override void Start(bool updateEnabled = false, bool setEnabledTo = false) { if (updateEnabled) { myConfig.setEnabled(setEnabledTo); } Stop(); running = true; master.BotClientNoticeEvent += BotClientRestart; LogFormater.Info("DataStore Service [Starting]"); } public override void Stop() { if(running == true) { LogFormater.Info("DataStore Service [Stopping]"); } running = false; Reset(); master.BotClientNoticeEvent -= BotClientRestart; if (master.BotClient != null) { if (GetClient() != null) { GetClient().Groups.CurrentGroups -= GroupCurrent; GetClient().Groups.GroupJoinedReply -= GroupJoin; GetClient().Groups.GroupLeaveReply -= GroupLeave; GetClient().Avatars.UUIDNameReply -= AvatarUUIDName; GetClient().Friends.FriendNames -= AvatarFriendNames; GetClient().Self.IM -= ImEvent; GetClient().Self.ChatFromSimulator -= ChatFromSim; GetClient().Avatars.AvatarPickerReply -= AvatarUUIDNamePicker; GetClient().Groups.GroupMembersReply -= GroupMembers; GetClient().Groups.GroupRoleDataReply -= GroupRoles; GetClient().Estate.EstateBansReply -= EstateBans; GetClient().Friends.FriendFoundReply -= FindFriendReply; GetClient().Avatars.AvatarPropertiesReply -= AvatarDetailsReply; GetClient().Avatars.AvatarGroupsReply -= AvatarGroupsReplyCallback; GetClient().Avatars.AvatarPicksReply -= AvatarPicksReply; GetClient().Avatars.AvatarClassifiedReply -= AvatarClassifiedsReply; GetClient().Avatars.PickInfoReply -= PickInfoReply; GetClient().Avatars.ClassifiedInfoReply -= ClassifiedInfoReply; } } } } public class CommandHistory(bool setStatus, string setCommand, string[] setArgs, long setUnixtime, string Setresults) { public string Command = setCommand; public string[] Args = setArgs; public long Unixtime = setUnixtime; public bool status = setStatus; public string results = Setresults; } public class FriendLocations(string SetName, Vector3 setPos, Vector3 setRegionPos) { public string Name = SetName; public Vector3 Pos = setPos; public Vector3 RegionPos = setRegionPos; public long time = SecondbotHelpers.UnixTimeNow(); } }