diff --git a/SecondBotEvents/Commands/Movement.cs b/SecondBotEvents/Commands/Movement.cs index 88d51e2..7d5c4be 100644 --- a/SecondBotEvents/Commands/Movement.cs +++ b/SecondBotEvents/Commands/Movement.cs @@ -197,6 +197,7 @@ namespace SecondBotEvents.Commands { return Failure("Error Unable to Teleport to location", new [] { region, x, y, z }); } + master.HomeboundService.markTeleport(); return BasicReply("Accepted", new [] { region, x, y, z }); } diff --git a/SecondBotEvents/Commands/Notecard.cs b/SecondBotEvents/Commands/Notecard.cs index a824396..0222091 100644 --- a/SecondBotEvents/Commands/Notecard.cs +++ b/SecondBotEvents/Commands/Notecard.cs @@ -26,8 +26,8 @@ namespace SecondBotEvents.Commands { return Failure("Content value is empty", new [] { collection, content }); } - return Failure("@todo notecard temp stroage"); - //return BasicReply("ok", "NotecardAdd", new [] { collection, content }); + master.DataStoreService.AppendKeyValue("notecard_"+collection, content); + return BasicReply("ok", new [] { collection, master.DataStoreService.GetKeyValue("notecard_"+collection).Length.ToString() }); } [About("Clears the contents of a collection")] @@ -40,7 +40,8 @@ namespace SecondBotEvents.Commands { return Failure("Collection value is empty", new [] { collection }); } - return Failure("@todo notecard storage"); + master.DataStoreService.ClearKeyValue("notecard_" + collection); + return BasicReply("ok"); } [About("Sends a notecard to a avatar using the text in the prebuilt collection [see NotecardAdd] and also clears the collection just before sending [see NotecardClear]")] @@ -67,7 +68,18 @@ namespace SecondBotEvents.Commands { return Failure("Invaild avatar uuid", new [] { avatar, collection, notecardname }); } - return Failure("@todo notecard storage"); + string content = master.DataStoreService.GetKeyValue("notecard_" + collection); + if(content.Length <= 3) + { + return Failure("No content in notecard storage ?", new[] { avatar, collection, notecardname }); + } + bool result = master.botClient.SendNotecard(notecardname, content, avataruuid); + if (result == false) + { + return Failure("Failed to create/send notecard", new[] { avatar, content, notecardname }); + } + master.DataStoreService.ClearKeyValue("notecard_" + collection); + return BasicReply("ok"); } [About("Creates and sends a notecard in one command good if you are using HTTP otherwise see [NotecardSend]")] @@ -93,7 +105,12 @@ namespace SecondBotEvents.Commands { return Failure("Invaild avatar uuid", new[] { avatar, content, notecardname }); } - return Failure("@todo notecard sending"); + bool result = master.botClient.SendNotecard(notecardname, content, avataruuid); + if(result == false) + { + return Failure("Failed to create/send notecard", new[] { avatar, content, notecardname }); + } + return BasicReply("ok"); } } } diff --git a/SecondBotEvents/Commands/StreamAdmin.cs b/SecondBotEvents/Commands/StreamAdmin.cs index cc25ea8..581b3a9 100644 --- a/SecondBotEvents/Commands/StreamAdmin.cs +++ b/SecondBotEvents/Commands/StreamAdmin.cs @@ -60,7 +60,17 @@ namespace SecondBotEvents.Commands { return Failure("Notecard title is to short", new [] { endpoint, endpointcode }); } - return Failure("@todo notecard sending"); + ProcessAvatar(server_reply.AvatarUUID); + if(avataruuid == UUID.Zero) + { + return Failure("Unable to unpack avatar", new[] { endpoint, endpointcode }); + } + bool result = master.botClient.SendNotecard(server_reply.NotecardTitle, server_reply.NotecardContent, avataruuid); + if (result == false) + { + return Failure("Failed to create/send notecard", new[] { endpoint, endpointcode }); + } + return BasicReply("ok"); } catch (Exception e) { diff --git a/SecondBotEvents/Config/DataStoreConfig.cs b/SecondBotEvents/Config/DataStoreConfig.cs index 1d3d702..f286e72 100644 --- a/SecondBotEvents/Config/DataStoreConfig.cs +++ b/SecondBotEvents/Config/DataStoreConfig.cs @@ -14,7 +14,6 @@ namespace SecondBotEvents.Config settings.Add("LocalChatHistoryLimit"); settings.Add("GroupChatHistoryLimitPerGroup"); settings.Add("ImChatHistoryLimit"); - settings.Add("NotecardStoragePurgeAfterMins"); settings.Add("PrefetchGroupMembers"); settings.Add("PrefetchGroupRoles"); settings.Add("PrefetchEstateBanlist"); @@ -73,10 +72,5 @@ namespace SecondBotEvents.Config { return ReadSettingAsInt("ImChatHistoryLimit", 50); } - - public int GetNotecardStoragePurgeAfterMins() - { - return ReadSettingAsInt("NotecardStoragePurgeAfterMins", 10); - } } } \ No newline at end of file diff --git a/SecondBotEvents/Config/HomeboundConfig.cs b/SecondBotEvents/Config/HomeboundConfig.cs new file mode 100644 index 0000000..1251762 --- /dev/null +++ b/SecondBotEvents/Config/HomeboundConfig.cs @@ -0,0 +1,53 @@ +using OpenMetaverse; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class HomeboundConfig : Config + { + public HomeboundConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { } + protected override void MakeSettings() + { + filename = "homebound"; + settings.Add("Enabled"); + settings.Add("HomeSimSlUrl"); + settings.Add("BackupSimSLUrl"); + settings.Add("AtHomeSeekLocation"); + settings.Add("AtBackupSeekLocation"); + settings.Add("AtHomeAutoSitUuid"); + } + + public string GetAtHomeAutoSitUuid() + { + return ReadSettingAsString("AtHomeAutoSitUuid", UUID.Zero.ToString()); + } + + public bool GetAtHomeSeekLocation() + { + return ReadSettingAsBool("AtHomeSeekLocation", true); + } + + public bool GetAtBackupSeekLocation() + { + return ReadSettingAsBool("AtBackupSeekLocation", false); + } + + public string GetBackupSimSLUrl() + { + return ReadSettingAsString("BackupSimSLUrl", "Sandbox%20Decorus/128/128/27"); + } + + public string GetHomeSimSlUrl() + { + return ReadSettingAsString("HomeSimSlUrl", "Viserion/46/163/23"); + } + + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", false); + } + } + +} \ No newline at end of file diff --git a/SecondBotEvents/Config/HttpConfig.cs b/SecondBotEvents/Config/HttpConfig.cs index 866ff52..7089301 100644 --- a/SecondBotEvents/Config/HttpConfig.cs +++ b/SecondBotEvents/Config/HttpConfig.cs @@ -11,12 +11,12 @@ namespace SecondBotEvents.Config { filename = "http"; settings.Add("Enabled"); - settings.Add("Host"); + settings.Add("Port"); } - public string GetHost() + public int GetPort() { - return ReadSettingAsString("Host", "http://*:80"); + return ReadSettingAsInt("Port", 80); } public bool GetEnabled() diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs index 95a2013..387eac4 100644 --- a/SecondBotEvents/EventsSecondBot.cs +++ b/SecondBotEvents/EventsSecondBot.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Reflection; using System.Threading; +using System.Collections.Generic; namespace SecondBotEvents { @@ -10,24 +11,31 @@ namespace SecondBotEvents { static void Main(string[] args) { - Console.WriteLine("Welcome to Secondbot [Events build] version: " + AssemblyInfo.GetGitHash()); + LogFormater.Info("Welcome to Secondbot [Events build] version: " + AssemblyInfo.GetGitHash()); EventsSecondBot worker = new EventsSecondBot(args); while(worker.Exit() == false) { + if(worker.Ready == true) + { + worker.Status(); + } Thread.Sleep(1000); } + LogFormater.Warn("Shutdown in 5 secs"); + Thread.Sleep(5000); } } public class EventsSecondBot { - public HttpService HttpService = null; - public CommandsService CommandsService = null; - public BotClientService botClient = null; - public DiscordService DiscordService = null; - public InteractionService InteractionService = null; - public DataStoreService DataStoreService = null; + public HttpService HttpService { get { return (HttpService) getService("HttpService"); } } + public CommandsService CommandsService { get { return (CommandsService)getService("CommandsService"); } } + public BotClientService botClient { get { return (BotClientService)getService("BotClientService"); } } + public DiscordService DiscordService { get { return (DiscordService)getService("DiscordService"); } } + public InteractionService InteractionService { get { return (InteractionService)getService("InteractionService"); } } + public DataStoreService DataStoreService { get { return (DataStoreService)getService("DataStoreService"); } } + public HomeboundService HomeboundService { get { return (HomeboundService)getService("HomeboundService"); } } private EventHandler botclient_eventNotices; @@ -45,6 +53,8 @@ namespace SecondBotEvents handler?.Invoke(this, new BotClientNotice(asRestart)); } + + public string GetVersion() { return AssemblyInfo.GetGitHash(); @@ -67,61 +77,120 @@ namespace SecondBotEvents StartServices(); } + public bool Ready = false; + public void RestartServices() { StopServices(); StartServices(); } + + protected Dictionary services = new Dictionary(); + + public BotServices getService(string classname) + { + if(services.ContainsKey(classname) == false) + { + return null; + } + return services[classname]; + } + protected BotServices RegisterService(string classname, bool autoStart = true) + { + BotServices instance = (BotServices)GetInstance("SecondBotEvents.Services." + classname); + services.Add(classname, instance); + lastStatus.Add(classname, "?"); + if (autoStart == true) + { + instance.Start(); + } + return instance; + } + + protected object GetInstance(string strFullyQualifiedName) + { + Type type = Type.GetType(strFullyQualifiedName); + if (type != null) + return Activator.CreateInstance(type, this); + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + type = asm.GetType(strFullyQualifiedName); + if (type != null) + return Activator.CreateInstance(type, this); + } + return null; + } + 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); + services = new Dictionary(); + lastStatus = new Dictionary(); + RegisterService("BotClientService", false); + RegisterService("DataStoreService"); + RegisterService("HttpService"); + RegisterService("CommandsService"); + RegisterService("DiscordService"); + RegisterService("InteractionService"); + RegisterService("HomeboundService"); if (botClient.IsLoaded() == false) { Console.WriteLine("Config is not loaded :("); exitNow = true; return; } - HttpService.Start(); - CommandsService.Start(); - DiscordService.Start(); - InteractionService.Start(); botClient.Start(); + Ready = true; + } + + Dictionary lastStatus = new Dictionary(); + long lastChange = 0; + public void Status() + { + string Output = ""; + string addon = ""; + foreach(KeyValuePair A in services) + { + string StatusMessage = A.Value.Status(); + if(StatusMessage != lastStatus[A.Key]) + { + Output = Output + addon + " [" + A.Key + "] ~ " + StatusMessage; + lastStatus[A.Key] = StatusMessage; + addon = " | "; + } + } + if(Output != "") + { + LogFormater.Info(Output); + lastChange = SecondbotHelpers.UnixTimeNow(); + return; + } + long dif = SecondbotHelpers.UnixTimeNow() - lastChange; + if(dif > 60) + { + string[] bits = lastStatus.Keys.ToArray(); + foreach (string A in bits) + { + lastStatus[A] = ""; + } + } + } + protected void StopServices() { - if(DataStoreService != null) + Ready = false; + Dictionary copy = services; + foreach (KeyValuePair A in copy) { - DataStoreService.Stop(); + if(A.Key == "BotClientService") + { + continue; + } + A.Value.Stop(); + services.Remove(A.Key); } - if (HttpService != null) - { - HttpService.Stop(); - } - if (CommandsService != null) - { - CommandsService.Stop(); - } - if (DiscordService != null) - { - DiscordService.Stop(); - } - if(InteractionService != null) - { - InteractionService.Stop(); - } - if(botClient != null) - { - botClient.Stop(); - } - HttpService = null; - botClient = null; - + services["BotClientService"].Stop(); + services.Remove("BotClientService"); } public bool Exit() diff --git a/SecondBotEvents/Services/BotClientService.cs b/SecondBotEvents/Services/BotClientService.cs index 038478c..0f5707c 100644 --- a/SecondBotEvents/Services/BotClientService.cs +++ b/SecondBotEvents/Services/BotClientService.cs @@ -1,4 +1,5 @@ using OpenMetaverse; +using OpenMetaverse.Assets; using SecondBotEvents.Config; using System; using System.Collections.Generic; @@ -6,10 +7,19 @@ using System.Text; namespace SecondBotEvents.Services { - public class BotClientService : Services + public class BotClientService : BotServices { public GridClient client = null; protected BasicConfig basicCfg = null; + public bool _ExitBot + { + get { return ExitBot; } // get method + set { } // set method + } + + protected bool ExitBot = false; + + public BotClientService(EventsSecondBot setMaster) : base(setMaster) { basicCfg = new BasicConfig(master.fromEnv, master.fromFolder); @@ -52,7 +62,14 @@ namespace SecondBotEvents.Services } protected void BotLoginStatus(object o, LoginProgressEventArgs e) - { + { + if(e.FailReason != "") + { + Console.WriteLine("Client service ~ {FAILED} Login status: " + e.FailReason.ToString()); + client.Network.Logout(); + ResetClient(); + return; + } Console.WriteLine("Client service ~ Login status: " + e.Status.ToString()); } public void SendIM(UUID avatar, string message) @@ -61,6 +78,64 @@ namespace SecondBotEvents.Services client.Self.InstantMessage(avatar, message); } + public bool SendNotecard(string name, string content, UUID sendToUUID) + { + bool returnstatus = true; + name = name + " " + DateTime.Now; + client.Inventory.RequestCreateItem( + client.Inventory.FindFolderForType(AssetType.Notecard), + name, + name + " Created via SecondBot notecard API", + AssetType.Notecard, + UUID.Random(), + InventoryType.Notecard, + PermissionMask.All, + (bool Success, InventoryItem item) => + { + if (Success) + { + AssetNotecard empty = new AssetNotecard { BodyText = "\n" }; + empty.Encode(); + client.Inventory.RequestUploadNotecardAsset(empty.AssetData, item.UUID, + (bool emptySuccess, string emptyStatus, UUID emptyItemID, UUID emptyAssetID) => + { + if (emptySuccess) + { + empty.BodyText = content; + empty.Encode(); + client.Inventory.RequestUploadNotecardAsset(empty.AssetData, emptyItemID, + (bool finalSuccess, string finalStatus, UUID finalItemID, UUID finalID) => + { + if (finalSuccess) + { + LogFormater.Info("Sending notecard now"); + client.Inventory.GiveItem(finalItemID, name, AssetType.Notecard, sendToUUID, false); + } + else + { + returnstatus = false; + LogFormater.Warn("Unable to request notecard upload"); + } + + }); + } + else + { + LogFormater.Warn("The fuck empty success notecard create"); + returnstatus = false; + } + }); + } + else + { + LogFormater.Warn("Unable to find default notecards folder"); + returnstatus = false; + } + } + ); + return returnstatus; + } + protected void ResetClient() { bool isRestart = false; @@ -96,6 +171,25 @@ namespace SecondBotEvents.Services client.Network.BeginLogin(loginParams); } - + public override string Status() + { + if (client == null) + { + return "No Client [Restart needed]"; + } + else if (client.Network == null) + { + return "No network - [Starting / Shutting down]"; + } + else if (client.Network.Connected == false) + { + return "Not connected"; + } + else if (client.Network.CurrentSim == null) + { + return "No sim"; + } + return client.Network.CurrentSim.Name + " " + client.Self.SimPosition.ToString(); + } } } diff --git a/SecondBotEvents/Services/Commands.cs b/SecondBotEvents/Services/Commands.cs index 466c5ae..5a213f7 100644 --- a/SecondBotEvents/Services/Commands.cs +++ b/SecondBotEvents/Services/Commands.cs @@ -11,7 +11,7 @@ using System.Net.Http; namespace SecondBotEvents.Services { - public class CommandsService : Services + public class CommandsService : BotServices { public CommandsConfig myConfig = null; public bool acceptNewCommands = false; @@ -120,6 +120,15 @@ namespace SecondBotEvents.Services acceptNewCommands = false; } + public override string Status() + { + if (acceptNewCommands == false) + { + return "Disabled"; + } + return "Enabled"; + } + protected void BotImMessage(object o, InstantMessageEventArgs e) { if(acceptNewCommands == false) @@ -346,7 +355,7 @@ namespace SecondBotEvents.Services public string command; public string signingCode; public string[] args; - public string replyTarget; + public string replyTarget = null; public bool accepted = false; public long unixtimeOfCommand = 0; public SignedCommand(string input, bool requireSigning, bool requireTimewindow, int windowSize, string secret) @@ -357,12 +366,27 @@ namespace SecondBotEvents.Services accepted = true; // just accept the command return; } + vaildate(requireTimewindow, windowSize, secret); + } + public SignedCommand(string setCommand, string setSigningCode, string[] setArgs, int setUnixtime, string setReplyTarget, bool requireTimewindow, int windowSize, string secret) + { + command = setCommand; + signingCode = setSigningCode; + args = setArgs; + unixtimeOfCommand = setUnixtime; + replyTarget = setReplyTarget; + vaildate(requireTimewindow, windowSize, secret); + } + + protected void vaildate(bool requireTimewindow, int windowSize, string secret) + { + accepted = false; long dif = SecondbotHelpers.UnixTimeNow() - unixtimeOfCommand; if ((requireTimewindow == true) && ((unixtimeOfCommand == 0) || (dif > windowSize))) // bad time window { return; } - if ((signingCode == null) ) // no signing code + if ((signingCode == null)) // no signing code { return; } @@ -379,9 +403,8 @@ namespace SecondBotEvents.Services return; // invaild signing code } accepted = true; - - } + protected void unpackInput(string input) { if (IsValidJson(input) == false) diff --git a/SecondBotEvents/Services/DataStoreService.cs b/SecondBotEvents/Services/DataStoreService.cs index 5c70368..68f2d93 100644 --- a/SecondBotEvents/Services/DataStoreService.cs +++ b/SecondBotEvents/Services/DataStoreService.cs @@ -9,7 +9,7 @@ using System.Threading; namespace SecondBotEvents.Services { - public class DataStoreService : Services + public class DataStoreService : BotServices { protected DataStoreConfig myConfig; protected bool botConnected = false; @@ -18,15 +18,6 @@ namespace SecondBotEvents.Services 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 groupsKey2Name = new Dictionary(); @@ -44,12 +35,24 @@ namespace SecondBotEvents.Services protected Dictionary KeyValueStore = new Dictionary(); protected Dictionary KeyValueStoreLastUsed = new Dictionary(); - protected List commandHistories = new List(); - public void addCommandToHistory(UUID Source, string command, string[] args) + public override string Status() { - CommandHistory A = new CommandHistory(Source, command, args, SecondbotHelpers.UnixTimeNow()); + if (myConfig == null) + { + return "DataStore Service [Config broken]"; + } + 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(); + return sum.ToString(); + } + + public void addCommandToHistory(bool status, string command, string[] args) + { + CommandHistory A = new CommandHistory(status, command, args, SecondbotHelpers.UnixTimeNow()); commandHistories.Add(A); int c = commandHistories.Count; while (c > myConfig.GetCommandHistoryLimit()) @@ -257,83 +260,92 @@ namespace SecondBotEvents.Services protected void recordChat(UUID sessionID, string fromname, string message) { - if(chatWindows.ContainsKey(sessionID) == false) + lock (chatWindows) { - 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--; + 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 closeChat(UUID owner) { - UUID sessionID = UUID.Zero; - if(chatWindowsOwner.ContainsValue(owner) == false) + lock (chatWindows) { - return; - } - foreach(KeyValuePair ownermap in chatWindowsOwner) - { - if(ownermap.Value != owner) + UUID sessionID = UUID.Zero; + if (chatWindowsOwner.ContainsValue(owner) == false) { - continue; + return; } - sessionID = ownermap.Key; + foreach (KeyValuePair ownermap in chatWindowsOwner) + { + if (ownermap.Value != owner) + { + continue; + } + sessionID = ownermap.Key; + } + if (sessionID == UUID.Zero) + { + return; + } + chatWindowsOwner.Remove(sessionID); + chatWindowsIsGroup.Remove(sessionID); + chatWindowsUnread.Remove(sessionID); + chatWindows.Remove(sessionID); } - if(sessionID == UUID.Zero) - { - return; - } - chatWindowsOwner.Remove(sessionID); - chatWindowsIsGroup.Remove(sessionID); - chatWindowsUnread.Remove(sessionID); - chatWindows.Remove(sessionID); } protected void openChatWindow(bool group, UUID sessionID, UUID ownerID) { - if (chatWindows.ContainsKey(sessionID) == true) + lock (chatWindows) { - 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 (chatWindows.ContainsKey(sessionID) == true) { - 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; // window already open } - return; // chat recovered + 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 } - chatWindows.Add(sessionID, new List()); - chatWindowsUnread.Add(sessionID, false); - chatWindowsIsGroup.Add(sessionID, group); - chatWindowsOwner.Add(sessionID, ownerID); - // new window now open } @@ -632,23 +644,29 @@ namespace SecondBotEvents.Services protected void GroupRoles(object o, GroupRolesDataReplyEventArgs e) { - if(groupRoles.ContainsKey(e.GroupID) == false) + lock (groupRoles) { - groupRoles.Add(e.GroupID, new Dictionary()); + if (groupRoles.ContainsKey(e.GroupID) == false) + { + groupRoles.Add(e.GroupID, new Dictionary()); + } + groupRoles[e.GroupID] = e.Roles; } - groupRoles[e.GroupID] = e.Roles; } protected void GroupMembers(object o, GroupMembersReplyEventArgs e) { - if(groupMembers.ContainsKey(e.GroupID) == true) + lock (groupMembers) { - groupMembers.Remove(e.GroupID); - } - groupMembers.Add(e.GroupID, new List()); - foreach (KeyValuePair a in e.Members) - { - groupMembers[e.GroupID].Add(a.Value.ID); + if (groupMembers.ContainsKey(e.GroupID) == true) + { + groupMembers.Remove(e.GroupID); + } + groupMembers.Add(e.GroupID, new List()); + foreach (KeyValuePair a in e.Members) + { + groupMembers[e.GroupID].Add(a.Value.ID); + } } } @@ -678,7 +696,10 @@ namespace SecondBotEvents.Services protected void EstateBans(object o, EstateBansReplyEventArgs e) { - estateBanlist = e.Banned; + lock (estateBanlist) + { + estateBanlist = e.Banned; + } } protected void BotLoggedIn(object o, SimConnectedEventArgs e) @@ -733,35 +754,38 @@ namespace SecondBotEvents.Services protected void GroupCurrent(object o, CurrentGroupsEventArgs e) { - List missingGroups = groupsKey2Name.Keys.ToList(); - foreach(Group G in e.Groups.Values) + lock (groupsKey2Name) { - missingGroups.Remove(G.ID); - if (groupsKey2Name.ContainsKey(G.ID) == true) + List missingGroups = groupsKey2Name.Keys.ToList(); + foreach (Group G in e.Groups.Values) { - continue; + 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); + } } - 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) + foreach (UUID G in missingGroups) { - continue; + if (groupsKey2Name.ContainsKey(G) == false) + { + continue; + } + string name = groupsKey2Name[G]; + groupsKey2Name.Remove(G); + closeChat(G); } - string name = groupsKey2Name[G]; - groupsKey2Name.Remove(G); - closeChat(G); } } @@ -777,11 +801,14 @@ namespace SecondBotEvents.Services protected void addAvatar(UUID id, string name) { - if (avatarsName2Key.ContainsValue(id) == true) + lock (avatarsName2Key) { - return; + if (avatarsName2Key.ContainsValue(id) == true) + { + return; + } + avatarsName2Key.Add(name, id); } - avatarsName2Key.Add(name, id); } protected void AvatarUUIDName(object o, UUIDNameReplyEventArgs e) @@ -878,14 +905,14 @@ namespace SecondBotEvents.Services public class CommandHistory { - public UUID Source = UUID.Zero; public string Command = ""; public string[] Args = new string[] { }; public long Unixtime = 0; + public bool status = false; - public CommandHistory(UUID setSource, string setCommand, string[] setArgs, long setUnixtime) + public CommandHistory(bool setStatus, string setCommand, string[] setArgs, long setUnixtime) { - Source = setSource; + status = setStatus; Command = setCommand; Args = setArgs; Unixtime = setUnixtime; diff --git a/SecondBotEvents/Services/DiscordService.cs b/SecondBotEvents/Services/DiscordService.cs index 58534e3..b18d812 100644 --- a/SecondBotEvents/Services/DiscordService.cs +++ b/SecondBotEvents/Services/DiscordService.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace SecondBotEvents.Services { - public class DiscordService : Services + public class DiscordService : BotServices { public Config.DiscordConfig myConfig = null; public bool acceptChat = false; diff --git a/SecondBotEvents/Services/HomeboundService.cs b/SecondBotEvents/Services/HomeboundService.cs new file mode 100644 index 0000000..6f0e939 --- /dev/null +++ b/SecondBotEvents/Services/HomeboundService.cs @@ -0,0 +1,308 @@ +using OpenMetaverse; +using SecondBotEvents.Config; +using System; + +namespace SecondBotEvents.Services +{ + public class HomeboundService : BotServices + { + protected HomeboundConfig myConfig; + protected bool botConnected = false; + + + protected SimSlURL home = null; + protected SimSlURL backup = null; + protected UUID sitTarget = UUID.Zero; + protected long lastAlertTeleport = 0; + protected bool evacWanted = false; + protected bool gotoPosAttemptedTp = false; + protected bool gotoPosAttemptedWalk = false; + protected long lastGotoAction = 0; + protected bool softDisable = false; + protected bool closeToHome = false; + + public void markTeleport() + { + softDisable = true; + } + + public HomeboundService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new HomeboundConfig(master.fromEnv, master.fromFolder); + if (myConfig.GetEnabled() == false) + { + return; + } + home = new SimSlURL(myConfig.GetHomeSimSlUrl()); + backup = new SimSlURL(myConfig.GetBackupSimSLUrl()); + if(UUID.TryParse(myConfig.GetAtHomeAutoSitUuid(), out sitTarget) == false) + { + sitTarget = UUID.Zero; + } + } + + protected string simNickname = ""; + protected bool attemptedTeleportHome = false; + protected bool attemptedTeleportBackup = false; + protected long teleportActionLockout = 0; + + protected void Tick() + { + if (evacWanted == true) + { + BotAlertMessage(null, new AlertMessageEventArgs("restart", "999", new OpenMetaverse.StructuredData.OSDMap())); + } + if (getClient().Network.CurrentSim.Name == home.name) + { + simNickname = "Home # "; + if (sitTarget != UUID.Zero) + { + if (getClient().Self.SittingOn == 0) + { + getClient().Self.RequestSit(sitTarget, Vector3.Zero); + } + } + if (myConfig.GetAtHomeSeekLocation() == true) + { + GetToPos(home); + } + return; + } + else if (getClient().Network.CurrentSim.Name == backup.name) + { + simNickname = "Backup # "; + if (myConfig.GetAtBackupSeekLocation() == true) + { + GetToPos(backup); + } + return; + } + if (simNickname == "") + { + simNickname = "Someplace # "; + return; + } + long dif = SecondbotHelpers.UnixTimeNow() - teleportActionLockout; + if(dif < 30) + { + return; + } + teleportActionLockout = SecondbotHelpers.UnixTimeNow(); + if (attemptedTeleportHome == false) + { + attemptedTeleportHome = true; + LogFormater.Info("Teleporting to home sim"); + getClient().Self.Teleport(home.name, new Vector3(home.x, home.y, home.z), new Vector3(0, 0, 0)); + return; + } + else if(attemptedTeleportBackup == false) + { + attemptedTeleportBackup = true; + LogFormater.Info("Teleporting to backup sim"); + teleportActionLockout = SecondbotHelpers.UnixTimeNow(); + getClient().Self.Teleport(backup.name, new Vector3(backup.x, backup.y, backup.z), new Vector3(0, 0, 0)); + return; + } + attemptedTeleportHome = false; + attemptedTeleportBackup = false; + LogFormater.Warn("Both home and backup sims failed clearing and trying again"); + } + + public override string Status() + { + if(myConfig == null) + { + return simNickname + "Config broken"; + } + if(botConnected == false) + { + return simNickname + "No bot"; + } + if(softDisable == false) + { + Tick(); + if(evacWanted == true) + { + return simNickname + "Waiting to evac"; + } + else if(getClient().Self.SittingOn != 0) + { + return simNickname + "Sitting on a thing"; + } + else if(closeToHome == true) + { + return simNickname + "In a safe space"; + } + else if(gotoPosAttemptedWalk == true) + { + return simNickname + "Trying to auto pilot"; + } + else if(gotoPosAttemptedTp == true) + { + return simNickname + "Trying to TP to location"; + } + return simNickname + "Waiting"; + } + return simNickname + "Standby - teleported by master / script"; + } + + protected void GetToPos(SimSlURL location) + { + if (getClient().Self.SittingOn != 0) + { + return; + } + long dif = SecondbotHelpers.UnixTimeNow() - lastGotoAction; + if (dif < 30) + { + return; + } + lastGotoAction = SecondbotHelpers.UnixTimeNow(); + float dist = Vector3.Distance(new Vector3(location.x, location.y, location.z), getClient().Self.SimPosition); + if(dist < 1) + { + closeToHome = true; + if (gotoPosAttemptedWalk == true) + { + getClient().Self.AutoPilotCancel(); + } + gotoPosAttemptedWalk = false; + gotoPosAttemptedTp = false; + return; + } + if (gotoPosAttemptedTp == false) + { + gotoPosAttemptedTp = true; + getClient().Self.Teleport(location.name, new Vector3(location.x, location.y, location.z), new Vector3(0, 0, 0)); + return; + } + if(gotoPosAttemptedWalk == false) + { + gotoPosAttemptedWalk = true; + getClient().Self.AutoPilotLocal(location.x, location.y, location.z); + return; + } + getClient().Self.AutoPilotCancel(); + gotoPosAttemptedWalk = false; + } + + protected void BotChangedSim(object o, SimChangedEventArgs e) + { + gotoPosAttemptedTp = false; + evacWanted = false; + gotoPosAttemptedWalk = false; + closeToHome = false; + simNickname = ""; + attemptedTeleportHome = false; + attemptedTeleportBackup = false; + lastGotoAction = SecondbotHelpers.UnixTimeNow(); + teleportActionLockout = SecondbotHelpers.UnixTimeNow() + 30; + } + + protected void BotAlertMessage(object o, AlertMessageEventArgs e) + { + if (e.Message.ToLower().Contains("restart") == false) + { + return; + } + long dif = SecondbotHelpers.UnixTimeNow() - lastAlertTeleport; + if (dif < 60) + { + if (evacWanted == false) + { + LogFormater.Warn("Evac wanted due to restart"); + evacWanted = true; + } + return; + } + evacWanted = false; + if (getClient().Network.CurrentSim.Name == home.name) + { + LogFormater.Info("Teleporting to backup sim to avoid restart"); + getClient().Self.Teleport(backup.name, new Vector3(backup.x, backup.y, backup.z), new Vector3(0, 0, 0)); + return; + } + LogFormater.Info("Teleporting to home sim to avoid restart"); + getClient().Self.Teleport(home.name, new Vector3(home.x, home.y, home.z), new Vector3(0, 0, 0)); + } + + protected void BotClientRestart(object o, BotClientNotice e) + { + botConnected = false; + Console.WriteLine("Homebound Service [Attached to new client]"); + getClient().Network.LoggedOut += BotLoggedOut; + getClient().Network.SimConnected += BotLoggedIn; + getClient().Self.AlertMessage += BotAlertMessage; + getClient().Network.SimChanged += BotChangedSim; + } + + protected void BotLoggedOut(object o, LoggedOutEventArgs e) + { + lastAlertTeleport = 0; + evacWanted = false; + botConnected = false; + gotoPosAttemptedTp = false; + evacWanted = false; + gotoPosAttemptedWalk = false; + softDisable = false; + closeToHome = false; + lastGotoAction = SecondbotHelpers.UnixTimeNow(); + Console.WriteLine("Homebound Service [Standby]"); + } + protected void BotLoggedIn(object o, SimConnectedEventArgs e) + { + botConnected = true; + Console.WriteLine("Homebound Service [Active]"); + } + + public override void Start() + { + if(myConfig.GetEnabled() == false) + { + return; + } + Stop(); + master.BotClientNoticeEvent += BotClientRestart; + Console.WriteLine("Homebound Service [Starting]"); + } + + public override void Stop() + { + master.BotClientNoticeEvent -= BotClientRestart; + if (master.botClient != null) + { + if (getClient() != null) + { + getClient().Network.SimConnected -= BotLoggedIn; + getClient().Self.AlertMessage -= BotAlertMessage; + getClient().Network.SimChanged -= BotChangedSim; + } + } + Console.WriteLine("Homebound Service [Stopping]"); + } + } + + public class SimSlURL + { + public string name; + public int x = 0; + public int y = 0; + public int z = 0; + public SimSlURL(string url) + { + // http://maps.secondlife.com/secondlife/Viserion/66/166/23 + url = url.Replace("maps.secondlife.com/secondlife/", ""); + url = url.Replace("http://", ""); + url = url.Replace("https://", ""); + // Viserion/66/166/23 + string[] bits = url.Split("/"); + name = System.Net.WebUtility.UrlDecode(bits[0]); + x = int.Parse(bits[1]); + y = int.Parse(bits[2]); + z = int.Parse(bits[3]); + } + } +} + + + diff --git a/SecondBotEvents/Services/HttpService.cs b/SecondBotEvents/Services/HttpService.cs index 8f485bd..82f4708 100644 --- a/SecondBotEvents/Services/HttpService.cs +++ b/SecondBotEvents/Services/HttpService.cs @@ -1,18 +1,18 @@ using EmbedIO; using EmbedIO.Actions; +using EmbedIO.Routing; using EmbedIO.WebApi; +using Newtonsoft.Json; using OpenMetaverse; -using SecondBotEvents.Commands; using SecondBotEvents.Config; using Swan.Logging; using System; using System.Collections.Generic; -using System.Net; using System.Threading; namespace SecondBotEvents.Services { - public class HttpService : Services + public class HttpService : BotServices { protected WebServer HTTPendpoint = null; protected HttpConfig myConfig; @@ -27,26 +27,31 @@ namespace SecondBotEvents.Services } } + public bool GetAcceptBotCommands() + { + return acceptBotCommands; + } + public override string Status() { if (myConfig == null) { - return "HTTP service [Config status broken]"; + return "Config broken"; } if (myConfig.GetEnabled() == false) { - return "HTTP service [- Not requested -]"; + return "- Not requested -"; } // @todo HTTP server check here if (HTTPendpoint == null) { - return "HTTP endpoint not online"; + return "not online"; } if (acceptBotCommands == false) { - return "Active [Service]"; + return "Active ~ Service"; } - return "Active [Commands]"; + return "Active ~ Commands"; } protected void BotClientRestart(object o, BotClientNotice e) @@ -68,7 +73,7 @@ namespace SecondBotEvents.Services { getClient().Network.SimConnected -= BotLoggedIn; acceptBotCommands = true; - Console.WriteLine("HTTP service [Bot commands enabled]"); + CreateWebServer(); } public override void Start() @@ -80,8 +85,6 @@ namespace SecondBotEvents.Services } Stop(); master.BotClientNoticeEvent += BotClientRestart; - Console.WriteLine("HTTP service [Enabled]"); - CreateWebServer(); } public override void Stop() @@ -103,12 +106,14 @@ namespace SecondBotEvents.Services { Swan.Logging.Logger.UnregisterLogger(); HTTPendpoint = new WebServer(o => o - .WithUrlPrefix(myConfig.GetHost()) - .WithMode(HttpListenerMode.EmbedIO)) + .WithUrlPrefix("http://*:" + myConfig.GetPort().ToString()) + .WithMode(HttpListenerMode.EmbedIO)) .WithCors() + .WithWebApi("/api", m => m.WithController(() => new HttpWebBot(master))) .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" })) ); HTTPendpoint.RunAsync(); + Console.WriteLine("HTTP service [Enabled] on port "+ myConfig.GetPort().ToString()); } } @@ -170,13 +175,13 @@ namespace SecondBotEvents.Services protected object BasicReply(string input, string[] args, string command) { - // @todo Command history storage system + master.DataStoreService.addCommandToHistory(true, command, args); return new { reply = input, status = false }; } protected object Failure(string input, string command, string[] args, string whyfailed) { - // @todo Command history storage system + master.DataStoreService.addCommandToHistory(false, command, args); return new { reply = input, status = false }; } @@ -240,4 +245,51 @@ namespace SecondBotEvents.Services this.about = about; } } + + public class SecondbotWebApi : WebApiController + { + protected EventsSecondBot master; + public SecondbotWebApi(EventsSecondBot setmaster) + { + master = setmaster; + } + } + + public class HttpWebBot : SecondbotWebApi + { + public HttpWebBot(EventsSecondBot setmaster) : base(setmaster) { } + + [About("Runs a command on the bot")] + [ReturnHints("command reply")] + [ReturnHintsFailure("Bad token signing")] + [ArgHints("commandName", "the name of the command to run")] + [ArgHints("args", "a list of args formated with ~#~ as the split points")] + [ArgHints("signing", "a sha1 hash that meets level 3 API signing spec [see github wiki]")] + [ArgHints("unixtime", "the time you signed the command")] + [Route(HttpVerbs.Post, "/Run")] + + public string Run([FormField] string commandName, [FormField] string? args, [FormField] string signing, [FormField] string unixtime) + { + string input = commandName; + string[] myArgs = new string[] { }; + if(args != null) + { + myArgs = args.Split("~#~", StringSplitOptions.RemoveEmptyEntries); + } + if(int.TryParse(unixtime, out int cmdUnixtime) == false) + { + return "Invaild unixtime was sent"; + } + if (master.HttpService.GetAcceptBotCommands() == false) + { + return "No bot connected yet"; + } + SignedCommand C = new SignedCommand(commandName, signing, myArgs, cmdUnixtime, null, true, 5, master.CommandsService.myConfig.GetSharedSecret()); + if(C.accepted == false) + { + return "Command request rejected"; + } + return JsonConvert.SerializeObject(master.CommandsService.runCommand(C)); + } + } } diff --git a/SecondBotEvents/Services/InteractionService.cs b/SecondBotEvents/Services/InteractionService.cs index dc9d159..5901670 100644 --- a/SecondBotEvents/Services/InteractionService.cs +++ b/SecondBotEvents/Services/InteractionService.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace SecondBotEvents.Services { - public class InteractionService : Services + public class InteractionService : BotServices { protected InteractionConfig myConfig; protected bool botConnected = false; @@ -26,13 +26,17 @@ namespace SecondBotEvents.Services { if(myConfig == null) { - return "Interaction Service [Config status broken]"; + return "Config broken"; } if (myConfig.GetEnabled() == false) { - return "Interaction Service [- Not requested -]"; + return "- Not requested -"; } - return "Interaction Service [Waiting for bot]"; + if (botConnected == false) + { + return "Waiting for bot"; + } + return "Active"; } protected bool processRequest(bool enabledByConfig, string avatarName, string sourceName) @@ -90,12 +94,18 @@ namespace SecondBotEvents.Services return; } string mode = "Teleport Bot to Av"; + bool markTeleport = true; if (e.IM.Dialog == InstantMessageDialog.RequestLure) { mode = "Teleport Av to Bot"; + markTeleport = false; } if (processRequest(myConfig.GetAcceptTeleports(), e.IM.FromAgentName, mode) == false) { + if(markTeleport == true) + { + master.HomeboundService.markTeleport(); + } if (e.IM.Dialog != InstantMessageDialog.RequestLure) { return; diff --git a/SecondBotEvents/Services/Services.cs b/SecondBotEvents/Services/Services.cs index c4315a0..cbf1c8e 100644 --- a/SecondBotEvents/Services/Services.cs +++ b/SecondBotEvents/Services/Services.cs @@ -5,10 +5,10 @@ using System.Text; namespace SecondBotEvents.Services { - public abstract class Services + public abstract class BotServices { public EventsSecondBot master; - public Services(EventsSecondBot setMaster) + public BotServices(EventsSecondBot setMaster) { master = setMaster; }