SecondBot/SecondBotEvents/Services/DataStoreService.cs
2026-11-04 17:03:11 -07:00

2251 lines
80 KiB
C#

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<string, UUID> avatarsName2Key = [];
protected Dictionary<UUID, long> avatarLastUsed = [];
protected Dictionary<UUID, string> avatarsKey2Displayname = [];
protected Dictionary<UUID, string> groupsKey2Name = [];
protected Dictionary<UUID, Dictionary<UUID, GroupMember>> groupMembers = [];
protected Dictionary<UUID, Dictionary<UUID, GroupRole>> groupRoles = [];
protected Dictionary<UUID, KeyValuePair<string, long>> loginTokens = []; // Avatar -> {Token, Expiry}
protected List<string> localChatHistory = [];
protected Dictionary<UUID, List<string>> chatWindows = []; // sess, list chat
protected Dictionary<UUID, bool> chatWindowsUnread = []; // sess, bool
protected Dictionary<UUID, bool> chatWindowsIsGroup = []; // sess, bool
protected Dictionary<UUID, UUID> chatWindowsOwner = []; // sess, owner id
protected Dictionary<string, string> KeyValueStore = [];
protected Dictionary<string, long> KeyValueStoreLastUsed = [];
protected List<CommandHistory> commandHistories = [];
protected List<GroupInvite> PendingGroupInvites = [];
protected List<FriendRequest> PendingFriendRequests = [];
protected long lastCleanupKeyValueStore = 0;
protected long lastCleanupAvatarStore = 0;
protected List<string> acceptedStores = ["FriendRequest", "InventoryOffer", "GroupInvite", "Teleport"];
protected Dictionary<string, List<UUID>> 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<UUID,Group> 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<string> clearentrys = [];
foreach (KeyValuePair<string, long> 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<UUID, string> clearLinks = [];
foreach (KeyValuePair<string, UUID> entry in avatarsName2Key)
{
dif = unixtime - avatarLastUsed[entry.Value];
if (dif < (myConfig.GetAvatarsCleanAfterMins() * 60))
{
continue;
}
if (chatWindowsOwner.ContainsValue(entry.Value) == true)
{
continue;
}
clearLinks.Add(entry.Value, entry.Key);
}
lock (avatarLastUsed) lock (avatarsName2Key) lock(avatarsKey2Displayname)
{
foreach (KeyValuePair<UUID, string> 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<CommandHistory> 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<UUID, string> GetGroups()
{
return groupsKey2Name;
}
public List<UUID> GetAllGroupsChatWithUnread()
{
List<UUID> reply = [];
foreach(KeyValuePair<UUID, bool> 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<UUID,bool> check in chatWindowsIsGroup)
{
if(check.Value == false)
{
continue;
}
if(chatWindowsUnread[check.Key] == true)
{
reply = true;
break;
}
}
return reply;
}
public List<string> 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)
{
bool needFetch = false;
lock (groupMembers)
{
if (groupMembers.ContainsKey(group) == false || groupMembers[group].ContainsKey(avatar) == false)
{
needFetch = true;
}
}
if (needFetch)
{
AutoResetEvent fetchEvent = new(false);
void callback(object sender, GroupMembersReplyEventArgs e)
{
if (e.GroupID == group)
{
fetchEvent.Set();
}
}
try
{
GetClient().Groups.GroupMembersReply += callback;
GetClient().Groups.RequestGroupMembers(group);
fetchEvent.WaitOne(3000, false);
}
catch (Exception ex)
{
LogFormater.Warn($"GetGroupMemberRank exception during RequestGroupMembers: {ex.Message}");
}
finally
{
GetClient().Groups.GroupMembersReply -= callback;
}
}
lock (groupMembers)
{
if (groupMembers.ContainsKey(group) == false || groupMembers[group].ContainsKey(avatar) == false)
{
LogFormater.Info($"[DEBUG] GetGroupMemberRank: Returning Guest after fetch attempt. 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";
// Check for Owner powers (DeleteRole, ChangeActions, ChangeIdentity)
GroupPowers ownerPowers = GroupPowers.DeleteRole | GroupPowers.ChangeActions | GroupPowers.ChangeIdentity;
if ((m.Powers & ownerPowers) == ownerPowers) return "Owner";
if (string.IsNullOrEmpty(m.Title) == false)
{
if (m.Title.Contains("Owner", StringComparison.OrdinalIgnoreCase) || m.Title.Contains("Founder", StringComparison.OrdinalIgnoreCase))
{
return "Owner";
}
}
}
// Fetch group roles and role-member mapping to check assigned group roles
try
{
// Fetch group roles
Dictionary<UUID, GroupRole> rolesDict = new();
AutoResetEvent rolesFetchEvent = new(false);
void rolesCallback(object sender, GroupRolesDataReplyEventArgs e)
{
if (e.GroupID == group)
{
rolesDict = e.Roles;
rolesFetchEvent.Set();
}
}
GetClient().Groups.GroupRoleDataReply += rolesCallback;
GetClient().Groups.RequestGroupRoles(group);
rolesFetchEvent.WaitOne(2000, false);
GetClient().Groups.GroupRoleDataReply -= rolesCallback;
// Fetch group role-members map
List<KeyValuePair<UUID, UUID>> roleMembersList = new();
AutoResetEvent membersFetchEvent = new(false);
void roleMembersCallback(object sender, GroupRolesMembersReplyEventArgs e)
{
if (e.GroupID == group)
{
roleMembersList = e.RolesMembers;
membersFetchEvent.Set();
}
}
GetClient().Groups.GroupRoleMembersReply += roleMembersCallback;
GetClient().Groups.RequestGroupRolesMembers(group);
membersFetchEvent.WaitOne(2000, false);
GetClient().Groups.GroupRoleMembersReply -= roleMembersCallback;
// Check if avatar belongs to any Owner or Officer role
HashSet<UUID> ownerRoleIDs = new();
HashSet<UUID> officerRoleIDs = new();
foreach (KeyValuePair<UUID, GroupRole> r in rolesDict)
{
string rName = r.Value.Name ?? "";
string rTitle = r.Value.Title ?? "";
GroupPowers rPowers = r.Value.Powers;
GroupPowers ownerPowers = GroupPowers.DeleteRole | GroupPowers.ChangeActions | GroupPowers.ChangeIdentity;
if (rName.Equals("Owners", StringComparison.OrdinalIgnoreCase) ||
rName.Equals("Owner", StringComparison.OrdinalIgnoreCase) ||
rTitle.Contains("Owner", StringComparison.OrdinalIgnoreCase) ||
(rPowers & ownerPowers) == ownerPowers)
{
ownerRoleIDs.Add(r.Value.ID);
}
else if (rName.Contains("Officer", StringComparison.OrdinalIgnoreCase) || rTitle.Contains("Officer", StringComparison.OrdinalIgnoreCase))
{
officerRoleIDs.Add(r.Value.ID);
}
}
bool isOwnerRole = false;
bool isOfficerRole = false;
foreach (KeyValuePair<UUID, UUID> pair in roleMembersList)
{
if (pair.Value == avatar) // pair.Value = MemberID, pair.Key = RoleID
{
if (ownerRoleIDs.Contains(pair.Key))
{
isOwnerRole = true;
break;
}
if (officerRoleIDs.Contains(pair.Key))
{
isOfficerRole = true;
}
}
}
if (isOwnerRole) return "Owner";
if (isOfficerRole) return "Officer";
}
catch (Exception ex)
{
LogFormater.Warn($"GetGroupMemberRank role check exception: {ex.Message}");
}
lock (groupMembers)
{
if (groupMembers.ContainsKey(group) && groupMembers[group].ContainsKey(avatar))
{
GroupMember m = groupMembers[group][avatar];
if (string.IsNullOrEmpty(m.Title) == false && m.Title.Contains("Officer", StringComparison.OrdinalIgnoreCase))
{
return "Officer";
}
}
}
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<string, long>(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<UUID, string> GetGroupRoles(UUID group, bool force = false)
{
Dictionary<UUID, string> reply = [];
if (force || groupRoles.ContainsKey(group) == false)
{
AutoResetEvent fetchEvent = new(false);
void callback(object sender, GroupRolesDataReplyEventArgs e)
{
if (e.GroupID == group)
{
foreach (KeyValuePair<UUID, GroupRole> 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<UUID, GroupRole> 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<UUID, GroupMember>());
}
if (groupMembers[group].ContainsKey(avatar) == false)
{
reply = false;
AutoResetEvent fetchEvent = new(false);
void callback(object sender, GroupMembersReplyEventArgs e)
{
if (e.GroupID == group)
{
foreach (KeyValuePair<UUID, GroupMember> 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<UUID> GetGroupMembers(UUID Group)
{
lock (groupMembers)
{
if (groupMembers.ContainsKey(Group) == false)
{
groupMembers.Add(Group, new Dictionary<UUID, GroupMember>());
}
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 RecordAvatarIm(UUID avatarId, string fromName, string message)
{
lock (chatWindows)
{
UUID sessionID = UUID.Zero;
foreach (KeyValuePair<UUID, UUID> kvp in chatWindowsOwner)
{
if (kvp.Value == avatarId)
{
sessionID = kvp.Key;
break;
}
}
if (sessionID == UUID.Zero)
{
sessionID = avatarId;
OpenChatWindow(false, sessionID, avatarId);
}
RecordChat(sessionID, fromName, message);
}
}
public void RecordGroupChat(UUID groupId, string fromName, string message)
{
lock (chatWindows)
{
if (!chatWindows.ContainsKey(groupId))
{
OpenChatWindow(true, groupId, groupId);
}
RecordChat(groupId, fromName, message);
}
}
public void CloseChat(UUID owner)
{
lock (chatWindows)
{
UUID sessionID = UUID.Zero;
if (chatWindowsOwner.ContainsValue(owner) == false)
{
return;
}
foreach (KeyValuePair<UUID, UUID> 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<UUID, UUID> oldOwners = chatWindowsOwner;
foreach (KeyValuePair<UUID, UUID> 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<string, KeyValuePair<string, long>> notecardDataStore = [];
public List<string> 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<List<GroupInvite>>(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<GroupInvite> 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<FriendRequest> 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<Avatar> 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<UUID, string> av in e.Names)
{
if (av.Key == avatarId)
{
AddAvatar(av.Key, av.Value);
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<UUID, string> 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 && KnownAvatarUUID(avatarId) == true)
{
return true;
}
if (!string.IsNullOrEmpty(name) && KnownAvatarName(name) == true)
{
return true;
}
return false;
}
public void GetAvatarNames(List<UUID> avatars)
{
List<UUID> 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<string, UUID> 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<UUID,string> GetAvatarImWindows()
{
Dictionary<UUID, string> windows = [];
foreach (KeyValuePair<UUID,bool> 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<string> GetAvatarImWindow(UUID avatarId)
{
if (chatWindowsOwner.ContainsValue(avatarId) == false)
{
return [];
}
UUID sessionID = UUID.Zero;
foreach (KeyValuePair<UUID, UUID> 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<UUID, bool> 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<UUID> GetAvatarImWindowsUnread()
{
List<UUID> windows = [];
foreach (KeyValuePair<UUID, bool> 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<UUID, GroupMember>());
}
else
{
groupMembers[e.GroupID].Clear();
}
foreach (KeyValuePair<UUID, GroupMember> a in e.Members)
{
groupMembers[e.GroupID][a.Key] = a.Value;
}
}
}
public List<UUID> GetEstateBans()
{
if (estateBanlist == null)
{
List<UUID> 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<UUID> estateBanlist = null;
protected Dictionary<UUID, FriendLocations> 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<UUID, FriendInfo> 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<UUID, Avatar.AvatarProperties> avprops = [];
protected Dictionary<UUID, Dictionary<UUID, string>> avpicks = [];
protected Dictionary<UUID, Dictionary<UUID, string>> avclassifieds = [];
protected Dictionary<UUID, Dictionary<UUID, ProfilePick>> avpicksDetails = [];
protected Dictionary<UUID, Dictionary<UUID, ClassifiedAd>> avclassifiedsDetails = [];
protected Dictionary<UUID, List<AvatarGroup>> 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<bool, Dictionary<UUID, string>> GetAvatarPicks(UUID agentID)
{
if (!avpicks.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarPicks(agentID);
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
}
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avpicks[agentID]);
}
public KeyValuePair<bool, Dictionary<UUID, ProfilePick>> GetAvatarPicksDetailed(UUID agentID)
{
if (!avpicks.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarPicks(agentID);
return new KeyValuePair<bool, Dictionary<UUID, ProfilePick>>(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<UUID>(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<bool, Dictionary<UUID, ProfilePick>>(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<UUID> 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<UUID, Dictionary<UUID, string>> 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<bool, Dictionary<UUID, string>> GetAvatarClassifieds(UUID agentID)
{
if (!avclassifieds.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarClassified(agentID);
return new KeyValuePair<bool, Dictionary<UUID, string>>(false, []);
}
return new KeyValuePair<bool, Dictionary<UUID, string>>(true, avclassifieds[agentID]);
}
public KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>> GetAvatarClassifiedsDetailed(UUID agentID)
{
if (!avclassifieds.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarClassified(agentID);
return new KeyValuePair<bool, Dictionary<UUID, ClassifiedAd>>(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<bool, Dictionary<UUID, ClassifiedAd>>(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<UUID> 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<UUID, Dictionary<UUID, string>> 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<bool, Avatar.AvatarProperties> GetAvatarAvatarProperties(UUID agentID)
{
if (!avprops.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarProperties(agentID);
return new KeyValuePair<bool, Avatar.AvatarProperties>(false, new Avatar.AvatarProperties());
}
return new KeyValuePair<bool, Avatar.AvatarProperties>(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<bool, List<AvatarGroup>> GetAvatarGroups(UUID agentID)
{
if (!avgroups.ContainsKey(agentID))
{
GetClient().Avatars.RequestAvatarProperties(agentID);
return new KeyValuePair<bool, List<AvatarGroup>>(false, []);
}
return new KeyValuePair<bool, List<AvatarGroup>>(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<string> 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<InventoryBase> foldercontents = GetClient().Inventory.FolderContents(
folder,
GetClient().Self.AgentID, true, true,
InventorySortOrder.ByDate, TimeSpan.FromSeconds(30), false);
List<Task> 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<UUID> 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[name] = id;
avatarLastUsed[id] = SecondbotHelpers.UnixTimeNow();
if (avatarsKey2Displayname.ContainsKey(id) == false)
{
avatarsKey2Displayname.Add(id, "?");
}
displayNameFinder(id);
}
}
public string GetDisplayName(UUID id)
{
if (avatarsKey2Displayname.ContainsKey(id) && avatarsKey2Displayname[id] != "?" && !string.IsNullOrEmpty(avatarsKey2Displayname[id]))
{
return avatarsKey2Displayname[id];
}
lock (avatarsKey2Displayname)
{
if (!avatarsKey2Displayname.ContainsKey(id))
{
avatarsKey2Displayname[id] = "?";
}
}
AutoResetEvent waitEvent = new(false);
try
{
_ = GetClient().Avatars.GetDisplayNames(new List<UUID> { id }, (success, agents, badIds) =>
{
if (success && agents != null && agents.Length > 0)
{
foreach (var ag in agents)
{
if (ag.ID == id)
{
lock (avatarsKey2Displayname) lock (avatarsName2Key)
{
if (!string.IsNullOrEmpty(ag.DisplayName))
{
avatarsKey2Displayname[id] = ag.DisplayName;
}
if (!string.IsNullOrEmpty(ag.LegacyFullName))
{
avatarsName2Key[ag.LegacyFullName] = id;
}
}
break;
}
}
}
waitEvent.Set();
});
waitEvent.WaitOne(3000);
}
catch { }
// Fallback to RequestAgentProfile if GetDisplayNames didn't resolve
if (!avatarsKey2Displayname.ContainsKey(id) || avatarsKey2Displayname[id] == "?")
{
AutoResetEvent profileWait = new(false);
try
{
_ = GetClient().Avatars.RequestAgentProfile(id, (status, properties) =>
{
if (properties != null && !string.IsNullOrEmpty(properties.DisplayName))
{
lock (avatarsKey2Displayname)
{
avatarsKey2Displayname[id] = properties.DisplayName;
}
}
profileWait.Set();
});
profileWait.WaitOne(2000);
}
catch { }
}
if (avatarsKey2Displayname.ContainsKey(id) && avatarsKey2Displayname[id] != "?" && !string.IsNullOrEmpty(avatarsKey2Displayname[id]))
{
return avatarsKey2Displayname[id];
}
string avName = GetAvatarName(id);
if (!string.IsNullOrEmpty(avName) && avName != "lookup")
{
return avName;
}
return "?";
}
protected void AvatarUUIDName(object o, UUIDNameReplyEventArgs e)
{
foreach(KeyValuePair<UUID,string> pair in e.Names)
{
AddAvatar(pair.Key, pair.Value);
}
}
protected void AvatarUUIDNamePicker(object o, AvatarPickerReplyEventArgs e)
{
foreach(KeyValuePair<UUID,string> Av in e.Avatars)
{
AddAvatar(Av.Key, Av.Value);
}
}
protected void AvatarFriendNames(object o, FriendNamesEventArgs e)
{
foreach (KeyValuePair<UUID, string> 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();
}
}