From a5851c5fe1e0aa90f9a3edb782a131de3542ccc4 Mon Sep 17 00:00:00 2001 From: Madpeter <57852827+Madpeterz@users.noreply.github.com> Date: Sun, 29 May 2022 17:38:28 +0100 Subject: [PATCH] new data store for chat, groups and avaters --- SecondBotEvents/Commands/Animation.cs | 4 +- SecondBotEvents/Commands/Avatars.cs | 45 +- SecondBotEvents/Commands/Chat.cs | 45 +- SecondBotEvents/Commands/Group.cs | 4 +- SecondBotEvents/Commands/Inventory.cs | 4 +- SecondBotEvents/Config/BasicConfig.cs | 16 +- SecondBotEvents/Config/CommandsConfig.cs | 43 +- SecondBotEvents/Config/DataStoreConfig.cs | 45 ++ SecondBotEvents/Config/DiscordConfig.cs | 16 +- SecondBotEvents/Config/HttpConfig.cs | 8 +- SecondBotEvents/Config/InteractionConfig.cs | 58 ++ SecondBotEvents/EventsSecondBot.cs | 14 + SecondBotEvents/Services/Commands.cs | 24 +- SecondBotEvents/Services/DataStoreService.cs | 532 ++++++++++++++++++ SecondBotEvents/Services/HttpService.cs | 27 +- .../Services/InteractionService.cs | 225 ++++++++ .../Static/http_commands_helper.cs | 6 +- 17 files changed, 1028 insertions(+), 88 deletions(-) create mode 100644 SecondBotEvents/Config/DataStoreConfig.cs create mode 100644 SecondBotEvents/Config/InteractionConfig.cs create mode 100644 SecondBotEvents/Services/DataStoreService.cs create mode 100644 SecondBotEvents/Services/InteractionService.cs diff --git a/SecondBotEvents/Commands/Animation.cs b/SecondBotEvents/Commands/Animation.cs index 461deae..08e0e6e 100644 --- a/SecondBotEvents/Commands/Animation.cs +++ b/SecondBotEvents/Commands/Animation.cs @@ -5,9 +5,9 @@ using SecondBotEvents.Services; namespace SecondBotEvents.Commands { - internal class Animation : CommandsAPI + internal class AnimationCommands : CommandsAPI { - public Animation(EventsSecondBot setmaster) : base(setmaster) + public AnimationCommands(EventsSecondBot setmaster) : base(setmaster) { } diff --git a/SecondBotEvents/Commands/Avatars.cs b/SecondBotEvents/Commands/Avatars.cs index ff1fff6..3cdcf74 100644 --- a/SecondBotEvents/Commands/Avatars.cs +++ b/SecondBotEvents/Commands/Avatars.cs @@ -26,7 +26,7 @@ namespace SecondBotEvents.Commands } if (getClient().Network.CurrentSim == null) { - return BasicReply("Error not in a sim", "nearmewithdetails"); + return BasicReply("Error not in a sim"); } List BetterNearMe = new List(); Dictionary avcopy = getClient().Network.CurrentSim.ObjectsAvatars.Copy(); @@ -44,7 +44,7 @@ namespace SecondBotEvents.Commands BetterNearMe.Add(details); } } - return BasicReply(JsonConvert.SerializeObject(BetterNearMe), "nearmewithdetails"); + return BasicReply(JsonConvert.SerializeObject(BetterNearMe)); } [About("returns a list of all known avatars nearby")] @@ -59,7 +59,7 @@ namespace SecondBotEvents.Commands } if (getClient().Network.CurrentSim == null) { - return BasicReply("Error not in a sim", "nearme"); + return BasicReply("Error not in a sim"); } Dictionary NearMe = new Dictionary(); Dictionary avcopy = getClient().Network.CurrentSim.ObjectsAvatars.Copy(); @@ -70,7 +70,44 @@ namespace SecondBotEvents.Commands NearMe.Add(av.ID, av.Name); } } - return BasicReply(JsonConvert.SerializeObject(NearMe), "nearme"); + return BasicReply(JsonConvert.SerializeObject(NearMe)); + } + + [About("searchs the AV database for the given UUID to find the name, if not found triggers a lookup")] + [ReturnHints("the name of the avatar")] + [ReturnHints("lookup")] + [ReturnHintsFailure("Not a vaild UUID")] + [Route(HttpVerbs.Get, "/Key2Name/{uuid}/{token}")] + public object Key2Name(string uuid, string token) + { + if (AllowToken(token) == false) + { + return Failure("Token not accepted"); + } + UUID avUUID = UUID.Zero; + if (UUID.TryParse(uuid, out avUUID) == false) + { + return Failure("Not a vaild UUID"); + } + return BasicReply(master.DataStoreService.getAvatarName(avUUID)); + } + + [About("searchs the AV database for the given name to find the UUID, if not found triggers a lookup")] + [ReturnHints("the uuid of the avatar")] + [ReturnHints("lookup")] + [ReturnHintsFailure("Not a vaild name")] + [Route(HttpVerbs.Get, "/Name2Key/{name}/{token}")] + public object Name2Key(string name, string token) + { + if (AllowToken(token) == false) + { + return Failure("Token not accepted"); + } + if(name != null) + { + return Failure("Not a vaild name"); + } + return BasicReply(master.DataStoreService.getAvatarUUID(name)); } } } diff --git a/SecondBotEvents/Commands/Chat.cs b/SecondBotEvents/Commands/Chat.cs index a8d890a..d581347 100644 --- a/SecondBotEvents/Commands/Chat.cs +++ b/SecondBotEvents/Commands/Chat.cs @@ -13,8 +13,9 @@ namespace SecondBotEvents.Commands public Chat(EventsSecondBot setmaster) : base(setmaster) { } - [About("fetchs the last 20 localchat messages")] - [ReturnHints("array string")] + + [About("fetchs the last localchat messages")] + [ReturnHints("json encoded array")] [Route(HttpVerbs.Get, "/LocalChatHistory/{token}")] public object LocalChatHistory(string token) { @@ -22,7 +23,7 @@ namespace SecondBotEvents.Commands { return Failure("Token not accepted"); } - return Failure("@todo add local chat history"); + return BasicReply(JsonConvert.SerializeObject(master.DataStoreService.getLocalChat())); } [About("sends a message to localchat")] @@ -51,11 +52,7 @@ namespace SecondBotEvents.Commands return Failure("Invaild channel", "Say", new [] { channel, message }); } getClient().Self.Chat(message, channelnum, ChatType.Normal); - if (channelnum == 0) - { - // @todo add message to localchat - } - return Failure("@todo get localchat history"); + return BasicReply("ok"); } @@ -85,7 +82,7 @@ namespace SecondBotEvents.Commands return BasicReply("ok", "IM", new [] { avatar, message }); } - [About("gets a full list of all chat windows")] + [About("gets a full list of all avatar chat windows")] [ReturnHints("array UUID = Name")] [Route(HttpVerbs.Get, "/chatwindows/{token}")] public object chatwindows(string token) @@ -94,10 +91,10 @@ namespace SecondBotEvents.Commands { return Failure("Token not accepted"); } - return Failure("@todo chat windows list"); + return BasicReply(JsonConvert.SerializeObject(master.DataStoreService.getAvatarImWindows())); } - [About("gets a list of chat windows with unread messages")] + [About("gets a list of chat windows from avatars with unread messages")] [ReturnHints("array of UUID")] [Route(HttpVerbs.Get, "/listwithunread/{token}")] public object listwithunread(string token) @@ -106,11 +103,10 @@ namespace SecondBotEvents.Commands { return Failure("Token not accepted"); } - List unreadimwindows = new List(); - return Failure("@todo chat windows list unread"); + return BasicReply(JsonConvert.SerializeObject(master.DataStoreService.getAvatarImWindowsUnread())); } - [About("gets if there are any unread im messages at all")] + [About("gets if there are any unread im messages from avatars at all")] [ReturnHints("True|False")] [Route(HttpVerbs.Get, "/haveunreadims/{token}")] public object haveunreadims(string token) @@ -119,28 +115,27 @@ namespace SecondBotEvents.Commands { return Failure("Token not accepted"); } - return Failure("@todo have any unread ims"); + return BasicReply(master.DataStoreService.getAvatarImWindowsUnreadAny().ToString()); } - [About("gets the chat from the selected window")] - [ArgHints("window", "URLARG", "the UUID of the chat window")] - [ReturnHintsFailure("Window UUID invaild")] + [About("gets the chat from the selected window for avatar")] + [ArgHints("window", "URLARG", "the UUID of the avatar you wish to view the chat from")] + [ReturnHintsFailure("avatar UUID invaild")] [ReturnHints("Array of text")] - [Route(HttpVerbs.Get, "/getimchat/{window}/{token}")] - public object getimchat(string window, string token) + [Route(HttpVerbs.Get, "/getimchat/{avatarid}/{token}")] + public object getimchat(string avatarid, string token) { if (AllowToken(token) == false) { return Failure("Token not accepted"); } - if (UUID.TryParse(window, out UUID windowUUID) == false) + UUID avatarUUID = UUID.Zero; + if (UUID.TryParse(avatarid, out avatarUUID) == false) { - return Failure("Window UUID invaild", "getimchat", new[] { window }); + return Failure("avatar UUID invaild"); } - return Failure("@todo chat window fetch"); + return BasicReply(JsonConvert.SerializeObject(master.DataStoreService.getAvatarImWindow(avatarUUID))); } } - - } diff --git a/SecondBotEvents/Commands/Group.cs b/SecondBotEvents/Commands/Group.cs index 9f122c4..9106437 100644 --- a/SecondBotEvents/Commands/Group.cs +++ b/SecondBotEvents/Commands/Group.cs @@ -9,9 +9,9 @@ using System.Threading; namespace SecondBotEvents.Commands { - public class Group : CommandsAPI + public class GroupCommands : CommandsAPI { - public Group(EventsSecondBot setmaster) : base(setmaster) + public GroupCommands(EventsSecondBot setmaster) : base(setmaster) { } diff --git a/SecondBotEvents/Commands/Inventory.cs b/SecondBotEvents/Commands/Inventory.cs index 3f43a93..b0037ad 100644 --- a/SecondBotEvents/Commands/Inventory.cs +++ b/SecondBotEvents/Commands/Inventory.cs @@ -12,9 +12,9 @@ using System.Linq; namespace SecondBotEvents.Commands { - public class Inventory : CommandsAPI + public class InventoryCommands : CommandsAPI { - public Inventory(EventsSecondBot setmaster) : base(setmaster) + public InventoryCommands(EventsSecondBot setmaster) : base(setmaster) { } diff --git a/SecondBotEvents/Config/BasicConfig.cs b/SecondBotEvents/Config/BasicConfig.cs index 998ff23..b8ad30b 100644 --- a/SecondBotEvents/Config/BasicConfig.cs +++ b/SecondBotEvents/Config/BasicConfig.cs @@ -10,31 +10,31 @@ namespace SecondBotEvents.Config protected override void MakeSettings() { filename = "basic"; - settings.Add("username"); - settings.Add("password"); - settings.Add("loginURI"); + settings.Add("Username"); + settings.Add("Password"); + settings.Add("LoginURI"); } public string GetUsername() { - return ReadSettingAsString("username","Firstname Lastname"); + return ReadSettingAsString("Username","Firstname Lastname"); } public string GetPassword() { - return ReadSettingAsString("password", "passwordHere"); + return ReadSettingAsString("Password", "passwordHere"); } public string GetFirstName() { - string[] bits = ReadSettingAsString("username", "Firstname Lastname").Split(" "); + string[] bits = GetUsername().Split(" "); return bits[0]; } public string GetLastName() { - string[] bits = ReadSettingAsString("username", "Firstname Lastname").Split(" "); + string[] bits = GetUsername().Split(" "); if(bits.Length == 2) { return bits[1]; @@ -44,7 +44,7 @@ namespace SecondBotEvents.Config public string GetLoginURI() { - return ReadSettingAsString("loginURI", "secondlife"); + return ReadSettingAsString("LoginURI", "secondlife"); } } } diff --git a/SecondBotEvents/Config/CommandsConfig.cs b/SecondBotEvents/Config/CommandsConfig.cs index 84a211a..7189c0d 100644 --- a/SecondBotEvents/Config/CommandsConfig.cs +++ b/SecondBotEvents/Config/CommandsConfig.cs @@ -11,48 +11,55 @@ namespace SecondBotEvents.Config protected override void MakeSettings() { filename = "commands"; - settings.Add("allowFundsCommands"); - settings.Add("allowIMcontrol"); - settings.Add("sharedSecret"); - settings.Add("onlySelectedAvs"); - settings.Add("avsCSV"); - settings.Add("enforceTimeWindow"); - settings.Add("timeWindowSecs"); + settings.Add("AllowFundsCommands"); + settings.Add("AllowIMcontrol"); + settings.Add("SharedSecret"); + settings.Add("OnlyMasterAvs"); + settings.Add("MastersCSV"); + settings.Add("EnforceTimeWindow"); + settings.Add("TimeWindowSecs"); + settings.Add("Enabled"); + } + + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", true); + } + + public string[] GetMastersCSV() + { + return ReadSettingAsString("MastersCSV", "Madpeter Zond").Split(","); } public bool GetEnforceTimeWindow() { - return ReadSettingAsBool("enforceTimeWindow", false); + return ReadSettingAsBool("EnforceTimeWindow", false); } public int GetTimeWindowSecs() { - return ReadSettingAsInt("timeWindowSecs", 35); + return ReadSettingAsInt("TimeWindowSecs", 35); } - public bool GetOnlySelectedAvs() + public bool GetOnlyMasterAvs() { - return ReadSettingAsBool("onlySelectedAvs", false); + return ReadSettingAsBool("OnlyMasterAvs", false); } - public List GetAvsCSV() - { - return ReadSettingAsString("avsCSV", "Madpeter Zond,Madpeter Zond,Madpeter Zond").Split(",").ToList(); - } public bool GetAllowFundsCommands() { - return ReadSettingAsBool("allowFundsCommands", false); + return ReadSettingAsBool("AllowFundsCommands", false); } public bool GetAllowIMcontrol() { - return ReadSettingAsBool("allowIMcontrol", false); + return ReadSettingAsBool("AllowIMcontrol", false); } public string GetSharedSecret() { - return ReadSettingAsString("sharedSecret", "ThisIsMySecret"); + return ReadSettingAsString("SharedSecret", "ThisIsMySecret"); } } } diff --git a/SecondBotEvents/Config/DataStoreConfig.cs b/SecondBotEvents/Config/DataStoreConfig.cs new file mode 100644 index 0000000..5a01554 --- /dev/null +++ b/SecondBotEvents/Config/DataStoreConfig.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class DataStoreConfig : Config + { + public DataStoreConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { } + protected override void MakeSettings() + { + filename = "datastore"; + settings.Add("AvatarsPurgeAfterMins"); + settings.Add("LocalChatHistoryLimit"); + settings.Add("GroupChatHistoryLimitPerGroup"); + settings.Add("ImChatHistoryLimit"); + settings.Add("NotecardStoragePurgeAfterMins"); + } + + public int GetAvatarsPurgeAfterMins() + { + return ReadSettingAsInt("AvatarsPurgeAfterMins", 25); + } + + public int GetLocalChatHistoryLimit() + { + return ReadSettingAsInt("LocalChatHistoryLimit", 150); + } + + public int GetGroupChatHistoryLimitPerGroup() + { + return ReadSettingAsInt("GroupChatHistoryLimitPerGroup", 50); + } + + public int GetImChatHistoryLimit() + { + return ReadSettingAsInt("ImChatHistoryLimit", 50); + } + + public int GetNotecardStoragePurgeAfterMins() + { + return ReadSettingAsInt("NotecardStoragePurgeAfterMins", 10); + } + } +} \ No newline at end of file diff --git a/SecondBotEvents/Config/DiscordConfig.cs b/SecondBotEvents/Config/DiscordConfig.cs index 8a515af..1543df5 100644 --- a/SecondBotEvents/Config/DiscordConfig.cs +++ b/SecondBotEvents/Config/DiscordConfig.cs @@ -10,30 +10,30 @@ namespace SecondBotEvents.Config protected override void MakeSettings() { filename = "discord"; - settings.Add("enabled"); - settings.Add("serverID"); - settings.Add("clientToken"); - settings.Add("allowDiscordCommands"); + settings.Add("Enabled"); + settings.Add("ServerID"); + settings.Add("ClientToken"); + settings.Add("AllowDiscordCommands"); } public bool GetEnabled() { - return ReadSettingAsBool("enabled"); + return ReadSettingAsBool("Enabled"); } public ulong GetServerID() { - return ReadSettingAsUlong("serverID", 0); + return ReadSettingAsUlong("ServerID", 0); } public string GetClientToken() { - return ReadSettingAsString("clientToken","tokenPlzKThanks"); + return ReadSettingAsString("ClientToken","tokenPlzKThanks"); } public bool GetAllowDiscordCommands() { - return ReadSettingAsBool("allowDiscordCommands"); + return ReadSettingAsBool("AllowDiscordCommands"); } } } \ No newline at end of file diff --git a/SecondBotEvents/Config/HttpConfig.cs b/SecondBotEvents/Config/HttpConfig.cs index adfe803..866ff52 100644 --- a/SecondBotEvents/Config/HttpConfig.cs +++ b/SecondBotEvents/Config/HttpConfig.cs @@ -10,18 +10,18 @@ namespace SecondBotEvents.Config protected override void MakeSettings() { filename = "http"; - settings.Add("enabled"); - settings.Add("host"); + settings.Add("Enabled"); + settings.Add("Host"); } public string GetHost() { - return ReadSettingAsString("host", "http://*:80"); + return ReadSettingAsString("Host", "http://*:80"); } public bool GetEnabled() { - return ReadSettingAsBool("enabled", false); + return ReadSettingAsBool("Enabled", false); } } diff --git a/SecondBotEvents/Config/InteractionConfig.cs b/SecondBotEvents/Config/InteractionConfig.cs new file mode 100644 index 0000000..7601436 --- /dev/null +++ b/SecondBotEvents/Config/InteractionConfig.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class InteractionConfig : Config + { + public InteractionConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { } + protected override void MakeSettings() + { + filename = "interaction"; + settings.Add("AcceptTeleports"); + settings.Add("AcceptGroupInvites"); + settings.Add("AcceptInventory"); + settings.Add("EnableJsonOutputEvents"); + settings.Add("JsonOutputEventsTarget"); + settings.Add("AcceptFriendRequests"); + settings.Add("Enabled"); + } + + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", true); + } + + public bool GetAcceptTeleports() + { + return ReadSettingAsBool("AcceptTeleports", true); + } + + public bool GetAcceptGroupInvites() + { + return ReadSettingAsBool("AcceptGroupInvites", true); + } + + public bool GetAcceptInventory() + { + return ReadSettingAsBool("AcceptInventory", true); + } + + public string GetJsonOutputEventsTarget() + { + return ReadSettingAsString("JsonOutputEventsTarget", "none"); + } + + public bool GetEnableJsonOutputEvents() + { + return ReadSettingAsBool("EnableJsonOutputEvents", true); + } + + public bool GetAcceptFriendRequests() + { + return ReadSettingAsBool("AcceptFriendRequests", true); + } + } + +} \ No newline at end of file diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs index dc8583c..95a2013 100644 --- a/SecondBotEvents/EventsSecondBot.cs +++ b/SecondBotEvents/EventsSecondBot.cs @@ -26,6 +26,8 @@ namespace SecondBotEvents public CommandsService CommandsService = null; public BotClientService botClient = null; public DiscordService DiscordService = null; + public InteractionService InteractionService = null; + public DataStoreService DataStoreService = null; private EventHandler botclient_eventNotices; @@ -72,10 +74,13 @@ namespace SecondBotEvents } protected void StartServices() { + DataStoreService = new DataStoreService(this); + DataStoreService.Start(); botClient = new BotClientService(this); HttpService = new HttpService(this); CommandsService = new CommandsService(this); DiscordService = new DiscordService(this); + InteractionService = new InteractionService(this); if (botClient.IsLoaded() == false) { Console.WriteLine("Config is not loaded :("); @@ -85,10 +90,15 @@ namespace SecondBotEvents HttpService.Start(); CommandsService.Start(); DiscordService.Start(); + InteractionService.Start(); botClient.Start(); } protected void StopServices() { + if(DataStoreService != null) + { + DataStoreService.Stop(); + } if (HttpService != null) { HttpService.Stop(); @@ -101,6 +111,10 @@ namespace SecondBotEvents { DiscordService.Stop(); } + if(InteractionService != null) + { + InteractionService.Stop(); + } if(botClient != null) { botClient.Stop(); diff --git a/SecondBotEvents/Services/Commands.cs b/SecondBotEvents/Services/Commands.cs index be79f00..9b706eb 100644 --- a/SecondBotEvents/Services/Commands.cs +++ b/SecondBotEvents/Services/Commands.cs @@ -18,6 +18,10 @@ namespace SecondBotEvents.Services public CommandsService(EventsSecondBot setMaster) : base(setMaster) { myConfig = new CommandsConfig(master.fromEnv, master.fromFolder); + if (myConfig.GetEnabled() == false) + { + return; + } loadCommands(); } @@ -147,12 +151,12 @@ namespace SecondBotEvents.Services { break; } - if (myConfig.GetOnlySelectedAvs() == false) + if (myConfig.GetOnlyMasterAvs() == false) { break; } requireSigning = false; - acceptMessage = myConfig.GetAvsCSV().Contains(e.IM.FromAgentName); + acceptMessage = myConfig.GetMastersCSV().Contains(e.IM.FromAgentName); break; } default: @@ -181,7 +185,7 @@ namespace SecondBotEvents.Services } } - protected void SmartCommandReply(bool run_status, string target, string output, string command) + public void SmartCommandReply(bool run_status, string target, string output, string command) { string mode = "CHAT"; UUID target_avatar = UUID.Zero; @@ -194,12 +198,9 @@ namespace SecondBotEvents.Services { mode = "IM"; } - else + else if (int.TryParse(target, out target_channel) == false) { - if (int.TryParse(target, out target_channel) == false) - { - mode = "None"; - } + return; } if (mode == "CHAT") { @@ -318,6 +319,13 @@ namespace SecondBotEvents.Services { master.botClient.client.Network.SimConnected -= BotLoggedIn; master.botClient.client.Self.IM += BotImMessage; + if (myConfig.GetOnlyMasterAvs() == true) + { + foreach(string A in myConfig.GetMastersCSV()) + { + master.DataStoreService.knownAvatar(A); + } + } acceptNewCommands = true; Console.WriteLine("Commands service [accepting IM commands]"); } diff --git a/SecondBotEvents/Services/DataStoreService.cs b/SecondBotEvents/Services/DataStoreService.cs new file mode 100644 index 0000000..54a7018 --- /dev/null +++ b/SecondBotEvents/Services/DataStoreService.cs @@ -0,0 +1,532 @@ +using Newtonsoft.Json; +using OpenMetaverse; +using SecondBotEvents.Config; +using System; +using System.Linq; +using System.Collections; +using System.Collections.Generic; + +namespace SecondBotEvents.Services +{ + public class DataStoreService : Services + { + protected DataStoreConfig myConfig; + protected bool botConnected = false; + public DataStoreService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new DataStoreConfig(master.fromEnv,master.fromFolder); + } + + public override string Status() + { + if(myConfig == null) + { + return "DataStore Service [Config status broken]"; + } + return "DataStore Service [Waiting for bot]"; + } + + protected Dictionary avatarsName2Key = new Dictionary(); + protected Dictionary avatarsKey2Name = new Dictionary(); + + protected Dictionary groupsKey2Name = new Dictionary(); + protected Dictionary groupsName2Key = new Dictionary(); + + protected List localChatHistory = new List(); + + + protected Dictionary> chatWindows = new Dictionary>(); // sess, list chat + protected Dictionary chatWindowsUnread = new Dictionary(); // sess, bool + protected Dictionary chatWindowsIsGroup = new Dictionary(); // sess, bool + protected Dictionary chatWindowsOwner = new Dictionary(); // sess, owner id + + + protected void recordChat(UUID sessionID, string fromname, string message) + { + if(chatWindows.ContainsKey(sessionID) == false) + { + return; // chat window not open unable to record message + } + chatWindows[sessionID].Add(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--; + } + } + protected void openChatWindow(bool group, UUID sessionID, UUID ownerID) + { + 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, new List()); + chatWindowsUnread.Add(sessionID, false); + chatWindowsIsGroup.Add(sessionID, group); + chatWindowsOwner.Add(sessionID, ownerID); + // new window now open + } + + + protected Dictionary> notecardDataStore = new Dictionary>(); + + public List getLocalChat() + { + return localChatHistory; + } + + public bool knownGroup(UUID groupId, string name) + { + if(groupId != UUID.Zero) + { + return groupsKey2Name.ContainsKey(groupId); + } + return groupsName2Key.ContainsKey(name); + } + + public List getGroupChat(UUID groupId) + { + if(chatWindows.ContainsKey(groupId) == false) + { + return new List(); + } + return chatWindows[groupId]; + } + + /* + * 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); + } + + + protected int ObjectsAvatarsHash = 0; + /* + * checks if the avatar is known to the system + * if not the bot will attempt to + * lookup the avatar + */ + public bool knownAvatar(string name, UUID avatarId) + { + int currentHash = master.botClient.client.Network.CurrentSim.ObjectsAvatars.GetHashCode(); + if (currentHash != ObjectsAvatarsHash) + { + ObjectsAvatarsHash = currentHash; + List avs = master.botClient.client.Network.CurrentSim.ObjectsAvatars.Copy().Values.ToList(); + foreach (Avatar A in avs) + { + addAvatar(A.ID, A.Name); + } + } + if (avatarId != UUID.Zero) + { + if(avatarsKey2Name.ContainsKey(avatarId) == false) + { + master.botClient.client.Avatars.RequestAvatarName(avatarId); + return false; + } + return true; + } + string[] bits = name.Split(' '); + if (bits.Length == 1) + { + name = name + " Resident"; + } + if (avatarsName2Key.ContainsKey(name) == false) + { + master.botClient.client.Avatars.RequestAvatarNameSearch(name, UUID.Random()); + return false; + } + return true; + } + + public string getAvatarName(UUID avatarId) + { + if(knownAvatar(null, avatarId) == false) + { + return "lookup"; + } + return avatarsKey2Name[avatarId]; + } + + public string getAvatarUUID(string name) + { + if (knownAvatar(name) == false) + { + return "lookup"; + } + return avatarsName2Key[name].ToString(); + } + + public List getAvatarIms(UUID avatarId) + { + if(chatWindowsOwner.ContainsValue(avatarId) == false) + { + return new List(); + } + UUID sessionID = UUID.Zero; + foreach(KeyValuePair links in chatWindowsOwner) + { + if(links.Value == avatarId) + { + sessionID = links.Key; + break; + } + } + if (sessionID == UUID.Zero) + { + return new List(); + } + return chatWindows[sessionID]; + } + + public Dictionary getAvatarImWindows() + { + Dictionary windows = new Dictionary(); + 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 new List(); + } + UUID sessionID = UUID.Zero; + foreach (KeyValuePair links in chatWindowsOwner) + { + if (links.Value == avatarId) + { + sessionID = links.Key; + break; + } + } + if(chatWindowsIsGroup[sessionID] == true) + { + return new List(); + } + 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 = new List(); + 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() + { + avatarsName2Key = new Dictionary(); + avatarsKey2Name = new Dictionary(); + + groupsKey2Name = new Dictionary(); + groupsName2Key = new Dictionary(); + + localChatHistory = new List(); + + notecardDataStore = new Dictionary>(); + } + + + protected void BotClientRestart(object o, BotClientNotice e) + { + botConnected = false; + Console.WriteLine("DataStore Service [Attached to new client]"); + master.botClient.client.Network.LoggedOut += BotLoggedOut; + master.botClient.client.Network.SimConnected += BotLoggedIn; + } + + protected void BotLoggedOut(object o, LoggedOutEventArgs e) + { + botConnected = false; + master.botClient.client.Network.SimConnected += BotLoggedIn; + Console.WriteLine("DataStore Service [Standby]"); + } + + protected void BotLoggedIn(object o, SimConnectedEventArgs e) + { + master.botClient.client.Network.SimConnected -= BotLoggedIn; + master.botClient.client.Groups.CurrentGroups += GroupCurrent; + master.botClient.client.Groups.GroupJoinedReply += GroupJoin; + master.botClient.client.Groups.GroupLeaveReply += GroupLeave; + master.botClient.client.Avatars.UUIDNameReply += AvatarUUIDName; + master.botClient.client.Friends.FriendNames += AvatarFriendNames; + master.botClient.client.Self.IM += ImEvent; + master.botClient.client.Self.ChatFromSimulator += ChatFromSim; + master.botClient.client.Groups.RequestCurrentGroups(); + botConnected = true; + Console.WriteLine("DataStore Service [Active]"); + } + readonly string[] hard_blocked_agents = new[] { "secondlife", "second life" }; + protected void ChatFromSim(object o, ChatEventArgs e) + { + switch(e.Type) + { + case ChatType.Whisper: + case ChatType.Normal: + case ChatType.Shout: + { + if (e.SourceType != ChatSourceType.Agent) + { + break; + } + if (hard_blocked_agents.Contains(e.FromName.ToLowerInvariant()) == true) + { + break; + } + localChatHistory.Add(e.FromName + ": " + e.Message); + cleanLocalChat(); + break; + } + default: + { + break; + } + } + } + + protected void GroupCurrent(object o, CurrentGroupsEventArgs e) + { + List missingGroups = groupsKey2Name.Keys.ToList(); + foreach(Group G in e.Groups.Values) + { + missingGroups.Remove(G.ID); + if (groupsKey2Name.ContainsKey(G.ID) == true) + { + continue; + } + groupsKey2Name.Add(G.ID, G.Name); + groupsName2Key.Add(G.Name, G.ID); + master.botClient.client.Self.RequestJoinGroupChat(G.ID); + } + + foreach(UUID G in missingGroups) + { + if (groupsKey2Name.ContainsKey(G) == false) + { + continue; + } + string name = groupsKey2Name[G]; + groupsKey2Name.Remove(G); + groupsName2Key.Remove(name); + // @todo remove group chat here + } + } + + protected void GroupJoin(object o, GroupOperationEventArgs e) + { + master.botClient.client.Groups.RequestCurrentGroups(); + } + + protected void GroupLeave(object o, GroupOperationEventArgs e) + { + master.botClient.client.Groups.RequestCurrentGroups(); + } + + protected void addAvatar(UUID id, string name) + { + if (avatarsKey2Name.ContainsKey(id) == true) + { + return; + } + avatarsKey2Name.Add(id, name); + avatarsName2Key.Add(name, id); + } + + protected void AvatarUUIDName(object o, UUIDNameReplyEventArgs e) + { + foreach(KeyValuePair pair in e.Names) + { + addAvatar(pair.Key, pair.Value); + } + } + + protected void AvatarFriendNames(object o, FriendNamesEventArgs e) + { + foreach (KeyValuePair pair in e.Names) + { + addAvatar(pair.Key, pair.Value); + } + } + + protected void cleanGroupChat(UUID groupID) + { + if(groupsKey2Name.ContainsKey(groupID) == false) + { + return; + } + // @todo clean group chat here + } + + protected void cleanAvatarChat(UUID avatarID) + { + if (avatarsKey2Name.ContainsKey(avatarID) == false) + { + return; + } + // @todo clean avatar chat here + } + + protected void cleanLocalChat() + { + if (localChatHistory.Count <= myConfig.GetLocalChatHistoryLimit()) + { + return; + } + localChatHistory.RemoveAt(0); + } + + 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: + { + if (groupsKey2Name.ContainsKey(e.IM.IMSessionID) == true) + { + // group IM + openChatWindow(true, e.IM.IMSessionID, e.IM.IMSessionID); + recordChat(e.IM.IMSessionID, e.IM.FromAgentName, e.IM.Message); + break; + } + addAvatar(e.IM.FromAgentID, e.IM.FromAgentName); + openChatWindow(false, e.IM.IMSessionID, e.IM.FromAgentID); + recordChat(e.IM.IMSessionID, e.IM.FromAgentName, e.IM.Message); + break; + } + default: + { + break; + } + } + } + + public override void Start() + { + Stop(); + master.BotClientNoticeEvent += BotClientRestart; + Console.WriteLine("DataStore Service [Starting]"); + } + + public override void Stop() + { + reset(); + master.BotClientNoticeEvent -= BotClientRestart; + if (master.botClient != null) + { + if (master.botClient.client != null) + { + master.botClient.client.Groups.CurrentGroups -= GroupCurrent; + master.botClient.client.Groups.GroupJoinedReply -= GroupJoin; + master.botClient.client.Groups.GroupLeaveReply -= GroupLeave; + master.botClient.client.Avatars.UUIDNameReply -= AvatarUUIDName; + master.botClient.client.Friends.FriendNames -= AvatarFriendNames; + master.botClient.client.Self.IM -= ImEvent; + master.botClient.client.Self.ChatFromSimulator -= ChatFromSim; + } + } + Console.WriteLine("DataStore Service [Stopping]"); + } + } +} + + + diff --git a/SecondBotEvents/Services/HttpService.cs b/SecondBotEvents/Services/HttpService.cs index 8ca0528..21d6e86 100644 --- a/SecondBotEvents/Services/HttpService.cs +++ b/SecondBotEvents/Services/HttpService.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Net; using System.Threading; +using SecondBotEvents.Commands; namespace SecondBotEvents.Services { @@ -105,6 +106,23 @@ namespace SecondBotEvents.Services .WithUrlPrefix(myConfig.GetHost()) .WithMode(HttpListenerMode.EmbedIO)) .WithCors() + .WithWebApi("/animation", m => m.WithController(() => new AnimationCommands(master))) + .WithWebApi("/avatars", m => m.WithController(() => new Avatars(master))) + .WithWebApi("/chat", m => m.WithController(() => new Chat(master))) + .WithWebApi("/core", m => m.WithController(() => new Core(master))) + .WithWebApi("/dialogs", m => m.WithController(() => new Dialogs(master))) + .WithWebApi("/discord", m => m.WithController(() => new DiscordCommands(master))) + .WithWebApi("/estate", m => m.WithController(() => new Estate(master))) + .WithWebApi("/friends", m => m.WithController(() => new Friends(master))) + .WithWebApi("/funds", m => m.WithController(() => new Funds(master))) + .WithWebApi("/group", m => m.WithController(() => new GroupCommands(master))) + .WithWebApi("/info", m => m.WithController(() => new Info(master))) + .WithWebApi("/inventory", m => m.WithController(() => new InventoryCommands(master))) + .WithWebApi("/movement", m => m.WithController(() => new Movement(master))) + .WithWebApi("/notecard", m => m.WithController(() => new Notecard(master))) + .WithWebApi("/parcel", m => m.WithController(() => new ParcelCommands(master))) + .WithWebApi("/self", m => m.WithController(() => new Self(master))) + .WithWebApi("/streamadmin", m => m.WithController(() => new StreamAdmin(master))) .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" })) ); HTTPendpoint.RunAsync(); @@ -180,11 +198,12 @@ namespace SecondBotEvents.Services protected void ProcessAvatar(string avatar) { - string[] bits = avatar.Split(' '); avataruuid = UUID.Zero; - // @todo Avatar storage system - UUID.TryParse(avatar, out avataruuid); - + string UUIDfetch = master.DataStoreService.getAvatarUUID(avatar); + if(UUIDfetch != "lookup") + { + UUID.TryParse(UUIDfetch, out avataruuid); + } } protected void SuccessNoReturn(string command) diff --git a/SecondBotEvents/Services/InteractionService.cs b/SecondBotEvents/Services/InteractionService.cs new file mode 100644 index 0000000..545fe3a --- /dev/null +++ b/SecondBotEvents/Services/InteractionService.cs @@ -0,0 +1,225 @@ +using Newtonsoft.Json; +using OpenMetaverse; +using SecondBotEvents.Config; +using System; +using System.Linq; +using System.Collections; +using System.Collections.Generic; + +namespace SecondBotEvents.Services +{ + public class InteractionService : Services + { + protected InteractionConfig myConfig; + protected bool botConnected = false; + public InteractionService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new InteractionConfig(master.fromEnv,master.fromFolder); + if (myConfig.GetEnabled() == false) + { + Console.WriteLine(Status()); + return; + } + } + + public override string Status() + { + if(myConfig == null) + { + return "Interaction Service [Config status broken]"; + } + if (myConfig.GetEnabled() == false) + { + return "Interaction Service [- Not requested -]"; + } + return "Interaction Service [Waiting for bot]"; + } + + protected bool processRequest(bool enabledByConfig, string avatarName, string sourceName) + { + if(myConfig.GetEnabled() == false) + { + jsonOuput(false, sourceName, avatarName, "Interactions hooks disabled"); + return false; + } + if (enabledByConfig == false) + { + jsonOuput(false, sourceName, avatarName, "disabled by config"); + return false; + } + if (master.CommandsService.myConfig.GetOnlyMasterAvs() == false) + { + // master avatars list is not enabled. unable to continue + jsonOuput(false, sourceName, avatarName, "masters AV list is disabled"); + return false; + } + if (master.CommandsService.myConfig.GetMastersCSV().Contains(avatarName) == false) + { + jsonOuput(false, sourceName, avatarName, "Not from accepted master"); + return false; + } + return true; + } + + protected void BotClientRestart(object o, BotClientNotice e) + { + botConnected = false; + Console.WriteLine("Interaction Service [Attached to new client]"); + master.botClient.client.Network.LoggedOut += BotLoggedOut; + master.botClient.client.Network.SimConnected += BotLoggedIn; + } + + protected void jsonOuput(bool status, string eventype, string from, string why="", Dictionary setMisc = null) + { + if (myConfig.GetEnableJsonOutputEvents() == false) + { + return; + } + master.CommandsService.SmartCommandReply( + status, + myConfig.GetJsonOutputEventsTarget(), + JsonConvert.SerializeObject(new interactionEvent(from, eventype, status, why, setMisc)), + "interactions" + ); + } + + protected void BotTeleportOffer(object o, InstantMessageEventArgs e) + { + if((e.IM.Dialog != InstantMessageDialog.RequestTeleport) && (e.IM.Dialog != InstantMessageDialog.RequestLure)) + { + return; + } + string mode = "Teleport Bot to Av"; + if (e.IM.Dialog == InstantMessageDialog.RequestLure) + { + mode = "Teleport Av to Bot"; + } + if (processRequest(myConfig.GetAcceptTeleports(), e.IM.FromAgentName, mode) == false) + { + if (e.IM.Dialog != InstantMessageDialog.RequestLure) + { + return; + } + master.botClient.client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, false); + return; + } + jsonOuput(true, mode, e.IM.FromAgentName); + if (e.IM.Dialog == InstantMessageDialog.RequestLure) + { + master.botClient.client.Self.SendTeleportLure(e.IM.FromAgentID); + return; + } + master.botClient.client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true); + + } + + protected void BotGroupInviteOffer(object o, GroupInvitationEventArgs e) + { + if (processRequest(myConfig.GetAcceptGroupInvites(), e.FromName, "GroupInvite") == false) + { + return; + } + e.Accept = true; + jsonOuput(true, "GroupInvite", e.FromName); + } + + protected void BotInventoryOffer(object o, InventoryObjectOfferedEventArgs e) + { + if(processRequest(myConfig.GetAcceptInventory(),e.Offer.FromAgentName, "InventoryUpdate") == false) + { + return; + } + e.Accept = true; + Dictionary details = new Dictionary(); + details.Add("itemuuid", e.ObjectID.ToString()); + details.Add("itemtype", e.AssetType.ToString()); + InventoryItem itm = master.botClient.client.Inventory.FetchItem(e.ObjectID, master.botClient.client.Self.AgentID, 2000); + if(itm != null) + { + details.Add("itemname", itm.Name); + } + jsonOuput(true, "InventoryUpdate", e.Offer.FromAgentName, "see misc", details); + } + + protected void BotFriendRequested(object o, FriendshipOfferedEventArgs e) + { + if (processRequest(myConfig.GetAcceptGroupInvites(), e.AgentName, "FriendRequest") == false) + { + return; + } + master.botClient.client.Friends.AcceptFriendship(e.AgentID, e.SessionID); + jsonOuput(true, "FriendRequest", e.AgentName); + + } + + protected void BotLoggedOut(object o, LoggedOutEventArgs e) + { + botConnected = false; + master.botClient.client.Network.SimConnected += BotLoggedIn; + Console.WriteLine("Interaction Service [Standby]"); + } + + protected void BotLoggedIn(object o, SimConnectedEventArgs e) + { + master.botClient.client.Network.SimConnected -= BotLoggedIn; + master.botClient.client.Friends.FriendshipOffered += BotFriendRequested; + master.botClient.client.Inventory.InventoryObjectOffered += BotInventoryOffer; + master.botClient.client.Groups.GroupInvitation += BotGroupInviteOffer; + master.botClient.client.Self.IM += BotTeleportOffer; + botConnected = true; + Console.WriteLine("Interaction Service [Active]"); + } + + public override void Start() + { + if (myConfig.GetEnabled() == false) + { + Console.WriteLine("Interaction Service [- Not requested -]"); + return; + } + Stop(); + master.BotClientNoticeEvent += BotClientRestart; + Console.WriteLine("Interaction Service [Starting]"); + } + + public override void Stop() + { + if (myConfig.GetEnabled() == false) + { + return; + } + master.BotClientNoticeEvent -= BotClientRestart; + if (master.botClient != null) + { + if (master.botClient.client != null) + { + master.botClient.client.Friends.FriendshipOffered -= BotFriendRequested; + master.botClient.client.Inventory.InventoryObjectOffered -= BotInventoryOffer; + master.botClient.client.Groups.GroupInvitation -= BotGroupInviteOffer; + } + } + Console.WriteLine("Interaction Service [Stopping]"); + } + } + + public class interactionEvent + { + public string from = "?"; + public string eventType = "?"; + public bool accepted = false; + public string info = ""; + public Dictionary misc = new Dictionary(); + public interactionEvent(string setFrom, string setType, bool setAccepted, string setInfo, Dictionary setMisc = null) + { + accepted = setAccepted; + from = setFrom; + eventType = setType; + info = setInfo; + if(setMisc != null) + { + misc = setMisc; + } + } + } +} + diff --git a/SecondBotEvents/Static/http_commands_helper.cs b/SecondBotEvents/Static/http_commands_helper.cs index 1644a3c..d37b89a 100644 --- a/SecondBotEvents/Static/http_commands_helper.cs +++ b/SecondBotEvents/Static/http_commands_helper.cs @@ -10,7 +10,7 @@ namespace SecondBotEvents public static Dictionary getCommandModules() { Dictionary reply = new Dictionary(); - reply.Add("animation", typeof(Animation)); + reply.Add("animation", typeof(AnimationCommands)); reply.Add("avatars", typeof(Avatars)); reply.Add("chat", typeof(Chat)); reply.Add("core", typeof(Core)); @@ -19,9 +19,9 @@ namespace SecondBotEvents reply.Add("estate", typeof(Estate)); reply.Add("friends", typeof(Friends)); reply.Add("funds", typeof(Funds)); - reply.Add("group", typeof(Group)); + reply.Add("group", typeof(GroupCommands)); reply.Add("info", typeof(Info)); - reply.Add("inventory", typeof(Inventory)); + reply.Add("inventory", typeof(InventoryCommands)); reply.Add("movement", typeof(Movement)); reply.Add("notecard", typeof(Notecard)); reply.Add("parcel", typeof(ParcelCommands));