diff --git a/LibreMetaverse.StructuredData/JSON/OSDJson.cs b/LibreMetaverse.StructuredData/JSON/OSDJson.cs index d3ec21a..0ddef3a 100644 --- a/LibreMetaverse.StructuredData/JSON/OSDJson.cs +++ b/LibreMetaverse.StructuredData/JSON/OSDJson.cs @@ -20,7 +20,16 @@ namespace OpenMetaverse.StructuredData public static OSD DeserializeJson(string json) { - return DeserializeJson(JsonMapper.ToObject(json)); + try + { + JsonData A = JsonMapper.ToObject(json); + return DeserializeJson(A); + } + catch + { + return null; // something fucky happened to the input + } + } public static OSD DeserializeJson(JsonData json) diff --git a/LibreMetaverse/AppearanceManager.cs b/LibreMetaverse/AppearanceManager.cs index c660af5..54c950d 100644 --- a/LibreMetaverse/AppearanceManager.cs +++ b/LibreMetaverse/AppearanceManager.cs @@ -2091,30 +2091,37 @@ namespace OpenMetaverse // TODO: create Current Outfit Folder } - OSDMap request = new OSDMap(1) { ["cof_version"] = currentoutfitfolder.Version }; - - string msg = "Server side baking failed"; - - OSD res = null; - - await Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, request, cancellationToken, - (response, data, error) => res = OSDParser.Deserialize(data)); - - if (res is OSDMap result) + try { - if (result["success"]) - { - Logger.Log("Successfully set appearance", Helpers.LogLevel.Info, Client); - // TODO: Set local visual params and baked textures based on the result here - return true; - } - if (result.ContainsKey("error")) - { - msg += ": " + result["error"].AsString(); - } - } + OSDMap request = new OSDMap(1) { ["cof_version"] = currentoutfitfolder.Version }; - Logger.Log(msg, Helpers.LogLevel.Error, Client); + string msg = "Server side baking failed"; + + OSD res = null; + + await Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, request, cancellationToken, + (response, data, error) => res = OSDParser.Deserialize(data)); + + if (res is OSDMap result) + { + if (result["success"]) + { + Logger.Log("Successfully set appearance", Helpers.LogLevel.Info, Client); + // TODO: Set local visual params and baked textures based on the result here + return true; + } + if (result.ContainsKey("error")) + { + msg += ": " + result["error"].AsString(); + } + } + + Logger.Log(msg, Helpers.LogLevel.Error, Client); + } + catch (Exception e) + { + Logger.Log("UpdateAvatarAppearanceAsync has failed to unpack data: "+e.Message, Helpers.LogLevel.Error, Client); + } return false; } diff --git a/LibreMetaverse/GroupManager.cs b/LibreMetaverse/GroupManager.cs index ad72bfb..ea38895 100644 --- a/LibreMetaverse/GroupManager.cs +++ b/LibreMetaverse/GroupManager.cs @@ -1056,7 +1056,12 @@ namespace OpenMetaverse if (error != null) { return; } OSD result = OSDParser.Deserialize(data); - GroupMembersHandlerCaps(requestID, result); + if (result != null) + { + GroupMembersHandlerCaps(requestID, result); + return; + } + Logger.Log("Failed to json decode data from uri "+cap.AbsoluteUri+": ", Helpers.LogLevel.Error, Client); }); } else diff --git a/LibreMetaverse/InventoryManager.cs b/LibreMetaverse/InventoryManager.cs index 5bc8b49..65c116a 100644 --- a/LibreMetaverse/InventoryManager.cs +++ b/LibreMetaverse/InventoryManager.cs @@ -607,14 +607,17 @@ namespace OpenMetaverse try { OSD result = OSDParser.Deserialize(data); - OSDMap res = (OSDMap)result; - OSDArray itemsOSD = (OSDArray)res["items"]; - - foreach (var it in itemsOSD) + if (result != null) { - InventoryItem item = InventoryItem.FromOSD(it); - _Store[item.UUID] = item; - OnItemReceived(new ItemReceivedEventArgs(item)); + OSDMap res = (OSDMap)result; + OSDArray itemsOSD = (OSDArray)res["items"]; + + foreach (var it in itemsOSD) + { + InventoryItem item = InventoryItem.FromOSD(it); + _Store[item.UUID] = item; + OnItemReceived(new ItemReceivedEventArgs(item)); + } } } catch (Exception ex) diff --git a/LibreMetaverse/Settings.cs b/LibreMetaverse/Settings.cs index 2bcdc62..1732227 100644 --- a/LibreMetaverse/Settings.cs +++ b/LibreMetaverse/Settings.cs @@ -106,27 +106,27 @@ namespace OpenMetaverse /// Number of milliseconds before a CAPS call will time out /// Setting this too low will cause web requests time out and /// possibly retry repeatedly - public int CAPS_TIMEOUT = 60 * 1000; + public int CAPS_TIMEOUT = 63 * 1000; /// Number of milliseconds for xml-rpc to timeout - public int LOGIN_TIMEOUT = 60 * 1000; + public int LOGIN_TIMEOUT = 63 * 1000; /// Milliseconds before a packet is assumed lost and resent public int RESEND_TIMEOUT = 4000; /// Milliseconds without receiving a packet before the /// connection to a simulator is assumed lost - public int SIMULATOR_TIMEOUT = 60 * 1000; + public int SIMULATOR_TIMEOUT = 75 * 1000; /// Milliseconds to wait for a simulator info request through /// the grid interface public int MAP_REQUEST_TIMEOUT = 5 * 1000; /// Number of milliseconds between sending pings to each sim - public const int PING_INTERVAL = 2200; + public const int PING_INTERVAL = 3000; /// Number of milliseconds between sending camera updates - public const int DEFAULT_AGENT_UPDATE_INTERVAL = 500; + public const int DEFAULT_AGENT_UPDATE_INTERVAL = 1000; /// Number of milliseconds between updating the current /// positions of moving, non-accelerating and non-colliding objects @@ -134,7 +134,7 @@ namespace OpenMetaverse /// Millisecond interval between ticks, where all ACKs are /// sent out and the age of unACKed packets is checked - public const int NETWORK_TICK_INTERVAL = 500; + public const int NETWORK_TICK_INTERVAL = 650; #endregion #region Sizes diff --git a/SecondBotEvents/Commands/Self.cs b/SecondBotEvents/Commands/Self.cs index b8e6841..baac4e9 100644 --- a/SecondBotEvents/Commands/Self.cs +++ b/SecondBotEvents/Commands/Self.cs @@ -145,6 +145,7 @@ namespace SecondBotEvents.Commands [ReturnHints("ok")] public object Logoff() { + master.BotClient.flagLogoutExpected(); GetClient().Network.BeginLogout(); return BasicReply("ok"); } diff --git a/SecondBotEvents/Config/OnEventConfig.cs b/SecondBotEvents/Config/OnEventConfig.cs new file mode 100644 index 0000000..775a6dd --- /dev/null +++ b/SecondBotEvents/Config/OnEventConfig.cs @@ -0,0 +1,101 @@ +using OpenMetaverse; +using SecondBotEvents.Services; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class OnEventConfig : Config + { + public OnEventConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { } + protected override void MakeSettings() + { + filename = "onevent"; + settings.Add("Enabled"); + settings.Add("Count"); + settings.Add("HideStatusOutput"); + } + + public void unloadEvents() + { + int loop = 1; + while (loop <= GetCount()) + { + if (GetEventEnabled(loop) == false) + { + loop++; + continue; + } + RemoveSetting("Event" + loop.ToString() + "Enabled"); + RemoveSetting("Event" + loop.ToString() + "Source"); + RemoveSetting("Event" + loop.ToString() + "Monitor"); + int loop2 = 1; + while (loop2 <= GetWhereCount(loop)) + { + RemoveSetting("Event" + loop.ToString() + "Where" + loop2.ToString()); + loop2++; + } + RemoveSetting("Event" + loop.ToString() + "WhereCount"); + loop2 = 1; + while (loop2 <= GetActionCount(loop)) + { + settings.Remove("Event" + loop.ToString() + "Action" + loop2.ToString()); + loop2++; + } + RemoveSetting("Event" + loop.ToString() + "ActionCount"); + loop++; + } + } + + protected void RemoveSetting(string name) + { + mysettings.Remove(name); + settings.Remove(name); + } + + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", false); + } + + public int GetCount() + { + return ReadSettingAsInt("Count", 0); + } + + public int GetActionCount(int oneventid) + { + return ReadSettingAsInt("Event" + oneventid.ToString() + "ActionCount", 0); + } + + public string GetActionStep(int oneventid, int stepid) + { + return ReadSettingAsString("Event" + oneventid.ToString() + "Action" + stepid.ToString(), ""); + } + + public string GetWhereCheck(int oneventid, int checkid) + { + return ReadSettingAsString("Event" + oneventid.ToString() + "Where" + checkid.ToString(), ""); + } + + public int GetWhereCount(int oneventid) + { + return ReadSettingAsInt("Event" + oneventid.ToString() + "WhereCount", 0); + } + + public bool GetEventEnabled(int oneventid) + { + return ReadSettingAsBool("Event" + oneventid.ToString() + "Enabled", false); + } + + public string GetSource(int oneventid) + { + return ReadSettingAsString("Event" + oneventid.ToString() + "Source", ""); + } + public string GetSourceMonitor(int oneventid) + { + return ReadSettingAsString("Event" + oneventid.ToString() + "Monitor", ""); + } + } +} \ No newline at end of file diff --git a/SecondBotEvents/Config/RelayConfig.cs b/SecondBotEvents/Config/RelayConfig.cs index 7a6d390..0202f03 100644 --- a/SecondBotEvents/Config/RelayConfig.cs +++ b/SecondBotEvents/Config/RelayConfig.cs @@ -13,16 +13,6 @@ namespace SecondBotEvents.Config filename = "relay"; settings.Add("count"); settings.Add("HideStatusOutput"); - int loop = 1; - while(loop <= GetRelayCount()) - { - settings.Add(loop.ToString() + "_enabled"); - settings.Add(loop.ToString() + "_source_type"); - settings.Add(loop.ToString() + "_source_option"); - settings.Add(loop.ToString() + "_target_type"); - settings.Add(loop.ToString() + "_target_option"); - loop++; - } } public int GetRelayCount() diff --git a/SecondBotEvents/Config/config.cs b/SecondBotEvents/Config/config.cs index 4825d33..119e502 100644 --- a/SecondBotEvents/Config/config.cs +++ b/SecondBotEvents/Config/config.cs @@ -79,22 +79,26 @@ namespace SecondBotEvents.Config MakeSettingsFile(fromFolder); return; } - JObject result = JObject.Parse(IO.ReadFile(readfile)); - foreach (string setting in settings) + try { - if(result.ContainsKey(setting) == false) + JObject result = JObject.Parse(IO.ReadFile(readfile)); + foreach (var x in result) { - continue; + settings.Add(x.Key); + string value = x.Value.ToString(); + if (SecondbotHelpers.notempty(value) == false) + { + mysettings.Add(x.Key, ""); + continue; + } + mysettings.Add(x.Key, value); } - string value = result[setting].ToString(); - if (SecondbotHelpers.notempty(value) == false) - { - mysettings.Add(setting, ""); - continue; - } - mysettings.Add(setting, value); + loaded = true; + } + catch (Exception ex) + { + LogFormater.Warn("Json format for " + readfile + " is broken: " + ex.Message); } - loaded = true; } protected void LoadSettingsFromEnv() @@ -152,7 +156,8 @@ namespace SecondBotEvents.Config protected int ReadSettingAsInt(string key, int defaultValue = 30) { - bool result = int.TryParse(ReadSettingAsString(key, defaultValue.ToString()), out int value); + string read = ReadSettingAsString(key, defaultValue.ToString()); + bool result = int.TryParse(read, out int value); if (result == false) { return defaultValue; diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs index ca60329..9743909 100644 --- a/SecondBotEvents/EventsSecondBot.cs +++ b/SecondBotEvents/EventsSecondBot.cs @@ -42,6 +42,8 @@ namespace SecondBotEvents public EventsService EventsService { get { return (EventsService)GetService("EventsService"); } } public DialogService DialogService { get { return (DialogService)GetService("DialogService"); } } + public TriggerOnEventService TriggerOnEventService { get { return (TriggerOnEventService)GetService("TriggerOnEventService"); } } + public CurrentOutfitFolder CurrentOutfitFolder { get { return (CurrentOutfitFolder)GetService("CurrentOutfitFolder"); } } public RLVService RLV { get { return (RLVService)GetService("RLVService"); } } @@ -161,6 +163,7 @@ namespace SecondBotEvents RegisterService("EventsService"); RegisterService("DialogService"); RegisterService("CustomCommandsService"); + RegisterService("TriggerOnEventService"); if (BotClient.IsLoaded() == false) { LogFormater.Info("Config is not loaded :("); diff --git a/SecondBotEvents/Services/BotClientService.cs b/SecondBotEvents/Services/BotClientService.cs index 46da80a..c3809ce 100644 --- a/SecondBotEvents/Services/BotClientService.cs +++ b/SecondBotEvents/Services/BotClientService.cs @@ -15,6 +15,12 @@ namespace SecondBotEvents.Services public GridClient client = null; public BasicConfig basicCfg = null; + protected bool LogoutExpected = false; + public void flagLogoutExpected() + { + LogoutExpected = true; + } + protected bool ExitBot = false; protected Timer AutoRestartLoginTimer; @@ -78,6 +84,15 @@ namespace SecondBotEvents.Services protected void BotDisconnected(object o, DisconnectedEventArgs e) { LogFormater.Info("Client service ~ Network disconnected: " + e.Message); + if (LogoutExpected == false) + { + AutoRestartLoginTimer = new Timer(); + AutoRestartLoginTimer.Interval = 75 * 1000; + AutoRestartLoginTimer.AutoReset = false; + AutoRestartLoginTimer.Elapsed += RestartTimer; + LogFormater.Info("Client service ~ Unexpected disconnect (starting reconnect timer for 75s)"); + return; + } } protected void BotLoginStatus(object o, LoginProgressEventArgs e) @@ -163,7 +178,10 @@ namespace SecondBotEvents.Services bool isRestart = false; if (client != null) { - client.Network.Logout(); + if (client.Network != null) + { + client.Network.Logout(); + } isRestart = true; } client = null; diff --git a/SecondBotEvents/Services/CommandsService.cs b/SecondBotEvents/Services/CommandsService.cs index 936cfce..d429f1c 100644 --- a/SecondBotEvents/Services/CommandsService.cs +++ b/SecondBotEvents/Services/CommandsService.cs @@ -153,6 +153,7 @@ namespace SecondBotEvents.Services { break; } + master.DataStoreService.AddAvatar(e.IM.FromAgentID, e.IM.FromAgentName); requireSigning = false; acceptMessage = myConfig.GetMastersCSV().Contains(e.IM.FromAgentName); break; @@ -415,11 +416,9 @@ namespace SecondBotEvents.Services if (requireSigning == false) { accepted = true; // just accept the command - master.CommandNotice(command, source, String.Join("@@@", args), accepted); return; } Vaildate(requireTimewindow, windowSize, secret); - master.CommandNotice(command, source, String.Join("@@@", args), accepted); } public SignedCommand(CommandsService setMaster, string source, string setCommand, string setSigningCode, string[] setArgs, diff --git a/SecondBotEvents/Services/DataStoreService.cs b/SecondBotEvents/Services/DataStoreService.cs index 62bc1be..79cf00c 100644 --- a/SecondBotEvents/Services/DataStoreService.cs +++ b/SecondBotEvents/Services/DataStoreService.cs @@ -976,7 +976,7 @@ namespace SecondBotEvents.Services GetClient().Groups.RequestCurrentGroups(); } - protected void AddAvatar(UUID id, string name) + public void AddAvatar(UUID id, string name) { lock (avatarsName2Key) lock (avatarLastUsed) { diff --git a/SecondBotEvents/Services/EventsService.cs b/SecondBotEvents/Services/EventsService.cs index 9a7123f..e5e5dbb 100644 --- a/SecondBotEvents/Services/EventsService.cs +++ b/SecondBotEvents/Services/EventsService.cs @@ -25,6 +25,7 @@ namespace SecondBotEvents.Services protected UUID OutputAvatar = UUID.Zero; protected long lastGroupMembershipUpdate = 0; + public void InventoryUpdateEvent(Dictionary detail) { if (InventoryEvents.ContainsKey(detail["itemtype"]) == true) @@ -143,6 +144,7 @@ namespace SecondBotEvents.Services GetClient().Self.AlertMessage += BotAlertMessage; } + protected long lastAvUpdate = 0; protected string avatarhash = ""; protected void TrackAvatarsOnParcel() { @@ -152,6 +154,13 @@ namespace SecondBotEvents.Services GuestListLoaded = false; return; } + long dif = SecondbotHelpers.UnixTimeNow() - lastAvUpdate; + if(dif < 30) + { + // only check for changes every 30 secs + return; + } + lastAvUpdate = SecondbotHelpers.UnixTimeNow(); string hashstring = ""; List avs = new List(); @@ -471,4 +480,13 @@ namespace SecondBotEvents.Services Hash = SecondbotHelpers.GetSHA1(raw); } } + + public class GuestUpdateEvent + { + public string parcelName = ""; + public string regionName = ""; + public string avatarName = ""; + public UUID avatarUUID = UUID.Zero; + public bool entered = false; + } } diff --git a/SecondBotEvents/Services/InteractionService.cs b/SecondBotEvents/Services/InteractionService.cs index 284f515..21833b2 100644 --- a/SecondBotEvents/Services/InteractionService.cs +++ b/SecondBotEvents/Services/InteractionService.cs @@ -196,6 +196,7 @@ namespace SecondBotEvents.Services }; if(e.FromTask == false) { + master.DataStoreService.AddAvatar(e.Offer.FromAgentID, e.Offer.FromAgentName); details.Add("itemuuid", e.ObjectID.ToString()); details.Add("itemname", "?"); InventoryItem itm = GetClient().Inventory.FetchItem(e.ObjectID, GetClient().Self.AgentID, 2000); @@ -213,6 +214,7 @@ namespace SecondBotEvents.Services { return; } + master.DataStoreService.AddAvatar(e.AgentID, e.AgentName); GetClient().Friends.AcceptFriendship(e.AgentID, e.SessionID); JsonOuputCleaner(e.AgentName, "FriendRequest"); @@ -278,7 +280,6 @@ namespace SecondBotEvents.Services GetClient().Groups.GroupInvitation -= BotGroupInviteOffer; } } - } } diff --git a/SecondBotEvents/Services/TriggerOnEventService.cs b/SecondBotEvents/Services/TriggerOnEventService.cs new file mode 100644 index 0000000..cf27a9e --- /dev/null +++ b/SecondBotEvents/Services/TriggerOnEventService.cs @@ -0,0 +1,827 @@ +using OpenMetaverse; +using OpenMetaverse.Rendering; +using SecondBotEvents.Config; +using Swan.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Metadata.Ecma335; + +namespace SecondBotEvents.Services +{ + public class TriggerOnEventService : BotServices + { + protected OnEventConfig myConfig; + protected bool botConnected = false; + Dictionary> MyCustomEvents = new Dictionary>(); + Dictionary lockouts = new Dictionary(); + List trackEventGroups = new List(); + public TriggerOnEventService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new OnEventConfig(master.fromEnv, master.fromFolder); + int loop = 1; + string[] delimiterChars = new string[] { "{IS}", "{NOT}", "{IS_UUD}", "{MISSING}", "{CONTAINS}", "{IN_GROUP}", "{NOT_IN_GROUP}", "{LESSTHAN}", "{MORETHAN}", "{LOCKOUT}" }; + if(myConfig.GetCount() == 0) + { + myConfig.setEnabled(false); + LogFormater.Info("OnEvent Service - No events enabled stopping"); + } + while (loop <= myConfig.GetCount()) + { + if(myConfig.GetEventEnabled(loop) == false) + { + LogFormater.Info("OnEvent Service - Event "+loop.ToString()+" is not enabled"); + loop++; + continue; + } + CustomOnEvent Event = new CustomOnEvent(); + Event.Source = myConfig.GetSource(loop); + Event.MonitorFlags = myConfig.GetSourceMonitor(loop).Split("=#=").ToList(); + bool eventWhereOk = true; + int loop2 = 1; + while(loop2 <= myConfig.GetWhereCount(loop)) + { + string check = myConfig.GetWhereCheck(loop, loop2); + foreach (string delimiterChar in delimiterChars) + { + if(check.Contains(delimiterChar) == false) + { + continue; + } + string[] bits = check.Split(delimiterChar, StringSplitOptions.RemoveEmptyEntries); + if (bits.Length == 2) + { + string delimiterCharU = delimiterChar.Replace("{", ""); + delimiterCharU = delimiterCharU.Replace("}", ""); + delimiterCharU = delimiterCharU.Trim(); + Event.WhereChecksLeft.Add(bits[0].Trim()); + Event.WhereChecksCenter.Add(delimiterCharU); + Event.WhereChecksRight.Add(bits[1].Trim()); + } + else + { + eventWhereOk = false; + } + break; + } + loop2++; + } + if (eventWhereOk == false) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " failed where config checks"); + loop++; + continue; + } + Event.Source = myConfig.GetSource(loop); + Event.MonitorFlags = myConfig.GetSourceMonitor(loop).Split("=#=").ToList(); + if ((Event.Source == "GroupMemberJoin") || (Event.Source == "GroupMemberLeave")) + { + UUID groupUUID = UUID.Zero; + if (Event.MonitorFlags.Count != 1) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " GroupMember monitor flags not vaild"); + loop++; + continue; + } + if (UUID.TryParse(Event.MonitorFlags[0], out groupUUID) == false) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " GroupMember monitor flags not vaild"); + loop++; + continue; + } + if (trackEventGroups.Contains(groupUUID) == false) + { + trackEventGroups.Add(groupUUID); + } + } + else if ((Event.Source == "GuestJoins") || (Event.Source == "GuestLeaves")) + { + if(Event.MonitorFlags.Count != 2) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " Guest monitor flags not vaild"); + loop++; + continue; + } + } + else if (Event.Source == "AvatarIm") + { + if (Event.MonitorFlags.Count != 1) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " AvatarIm monitor flags not vaild"); + loop++; + continue; + } + UUID testUUID = UUID.Zero; + UUID.TryParse(Event.MonitorFlags[0],out testUUID); + if ((Event.MonitorFlags[0] != "Any") && (testUUID != UUID.Zero)) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " AvatarIm monitor flags not vaild"); + loop++; + continue; + } + } + loop2 = 1; + while (loop2 <= myConfig.GetActionCount(loop)) + { + Event.Actions.Add(myConfig.GetActionStep(loop, loop2)); + loop2++; + } + if(Event.Actions.Count == 0) + { + LogFormater.Info("OnEvent Service - Event " + loop.ToString() + " Actions are not setup"); + loop++; + continue; + } + if(MyCustomEvents.ContainsKey(Event.Source) == false) + { + MyCustomEvents.Add(Event.Source, new List()); + } + MyCustomEvents[Event.Source].Add(Event); + loop++; + } + myConfig.unloadEvents(); // remove unneeded copy of events from the config as we wont be using it + if (MyCustomEvents.Count == 0) + { + myConfig.setEnabled(false); + LogFormater.Info("OnEvent Service - failed to load any events stopping"); + } + } + + + protected void BotClientRestart(object o, BotClientNotice e) + { + botConnected = false; + LogFormater.Info("OnEvent 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("OnEvent Service [Standby]"); + } + + protected void BotImMessage(object o, InstantMessageEventArgs e) + { + switch (e.IM.Dialog) + { + case InstantMessageDialog.MessageFromObject: + { + break; + } + case InstantMessageDialog.MessageFromAgent: // shared with SessionSend + case InstantMessageDialog.SessionSend: + { + if (e.IM.GroupIM == true) + { + break; + } + master.DataStoreService.AddAvatar(e.IM.FromAgentID, e.IM.FromAgentName); + // trigger avatar IM + TriggerEvent("AvatarIm", new Dictionary() { + { "message", e.IM.Message} , + { "avatarname", e.IM.FromAgentName}, + { "avataruuid", e.IM.FromAgentID.ToString() }, + }); + break; + } + default: + { + break; + } + } + } + + protected void RemoveEvents() + { + if (master.BotClient != null) + { + if (GetClient() != null) + { + GetClient().Self.IM -= BotImMessage; + GetClient().Groups.GroupMembersReply -= BotGroupMembers; + GetClient().Groups.CurrentGroups -= BotGroupsCurrent; + GetClient().Network.SimChanged -= BotChangedSim; + GetClient().Self.MoneyBalance -= BotFundsEvent; + GetClient().Self.AlertMessage -= BotAlertMessage; + } + } + } + + protected void AttachEvents() + { + GetClient().Self.IM += BotImMessage; + GetClient().Groups.GroupMembersReply += BotGroupMembers; + GetClient().Groups.CurrentGroups += BotGroupsCurrent; + GetClient().Network.SimChanged += BotChangedSim; + GetClient().Self.MoneyBalance += BotFundsEvent; + GetClient().Self.AlertMessage += BotAlertMessage; + GetClient().Groups.RequestCurrentGroups(); + GetClient().Self.RequestBalance(); + } + + protected void BotAlertMessage(object o, AlertMessageEventArgs e) + { + TriggerEvent("SimAlert", new Dictionary() { + { "alertmessage", e.Message}, + }); + } + + protected void BotFundsEvent(object o, BalanceEventArgs e) + { + TriggerEvent("BalanceUpdate", new Dictionary() { + { "funds", e.Balance.ToString()}, + }); + } + + protected void BotGroupsCurrent(object o, CurrentGroupsEventArgs e) + { + lock (GroupMembership) lock (GroupMembershipUpdated) lock(GroupNames) + { + foreach(Group G in e.Groups.Values) + { + if (trackEventGroups.Contains(G.ID) == false) + { + return; + } + if (GroupNames.ContainsKey(G.ID) == true) + { + continue; + } + GroupNames.Add(G.ID, G.Name); + GroupMembership.Add(G.ID, new List()); + GroupMembershipUpdated.Add(G.ID, 0); + } + } + } + + protected Dictionary> GroupMembership = new Dictionary>(); + protected Dictionary GroupNames = new Dictionary(); + protected Dictionary GroupMembershipUpdated = new Dictionary(); + protected long lastGroupMembershipUpdate = 0; + // request group membership updates max of once per 60 secs + // request membership update for group once every 15 mins + protected void GroupMembershipUpkeep() + { + lock (GroupMembershipUpdated) + { + long dif = SecondbotHelpers.UnixTimeNow() - lastGroupMembershipUpdate; + if (dif < 60) + { + return; + } + UUID updateGroup = UUID.Zero; + foreach (KeyValuePair entry in GroupMembershipUpdated) + { + dif = SecondbotHelpers.UnixTimeNow() - entry.Value; + if (dif > (15 * 60)) + { + updateGroup = entry.Key; + break; + } + } + if (updateGroup != UUID.Zero) + { + lastGroupMembershipUpdate = SecondbotHelpers.UnixTimeNow(); + GroupMembershipUpdated[updateGroup] = SecondbotHelpers.UnixTimeNow(); + GetClient().Groups.RequestGroupMembers(updateGroup); + } + } + } + + protected void BotGroupMembers(object sender, GroupMembersReplyEventArgs e) + { + if(trackEventGroups.Contains(e.GroupID) == false) + { + return; + } + lock (GroupMembership) lock(GroupMembershipUpdated) + { + bool GroupLoaded = false; + if (GroupMembership.ContainsKey(e.GroupID) == true) + { + if (GroupMembership[e.GroupID].Count > 0) + { + GroupLoaded = true; + } + } + if (GroupLoaded == false) + { + foreach (GroupMember a in e.Members.Values) + { + GroupMembership[e.GroupID].Add(a.ID); + } + return; + } + List joined = new List(); + List tracked = new List(); + foreach (UUID a in GroupMembership[e.GroupID]) + { + tracked.Add(a); + } + master.DataStoreService.GetAvatarNames(tracked); + foreach (UUID a in e.Members.Keys) + { + if (tracked.Contains(a) == true) + { + tracked.Remove(a); + continue; + } + joined.Add(a); + + } + string groupname = "none"; + if(GroupNames.ContainsKey(e.GroupID) == true) + { + groupname = GroupNames[e.GroupID]; + } + foreach (UUID a in tracked) + { + // leave + GroupMembership[e.GroupID].Remove(a); + TriggerEvent("GroupMemberLeave", new Dictionary() { + { "groupuuid", e.GroupID.ToString()}, + { "groupname", groupname }, + { "avataruuid", a.ToString() } + }); + } + foreach (UUID a in joined) + { + // join + GroupMembership[e.GroupID].Add(a); + TriggerEvent("GroupMemberJoin", new Dictionary() { + { "groupuuid", e.GroupID.ToString()}, + { "groupname", groupname }, + { "avataruuid", a.ToString() } + }); + } + } + } + + protected string GetAvName(string avataruuid) + { + string avatarname = "lookup"; + if (avataruuid != null) + { + if (UUID.TryParse(avataruuid, out UUID avUUID) == true) + { + avatarname = master.DataStoreService.GetAvatarName(avUUID); + } + } + return avatarname; + } + + protected bool IsLockedout(string uuid) + { + lock (lockouts) + { + if (lockouts.ContainsKey(uuid) == false) + { + return false; + } + long now = SecondbotHelpers.UnixTimeNow(); + if(now >= lockouts[uuid]) + { + lockouts.Remove(uuid); + return false; + } + return true; + } + } + + protected void AddToLockout(string uuid, int mins) + { + lock (lockouts) + { + long newvalue = SecondbotHelpers.UnixTimeNow() + (mins * 60); + if (lockouts.ContainsKey(uuid) == true) + { + if (lockouts[uuid] < newvalue) + { + lockouts[uuid] = newvalue; + } + return; + } + lockouts.Add(uuid, newvalue); + } + } + + long lastCleanedLockouts = 0; + protected void CleanLockouts() + { + long dif = SecondbotHelpers.UnixTimeNow() - lastCleanedLockouts; + if(dif < 60) + { + return; + } + lastCleanedLockouts = SecondbotHelpers.UnixTimeNow(); + long now = SecondbotHelpers.UnixTimeNow(); + lock (lockouts) + { + List toberemoved = new List(); + foreach(KeyValuePair pair in lockouts) + { + if(pair.Value > now) + { + continue; + } + toberemoved.Add(pair.Key); + } + foreach(string A in toberemoved) + { + lockouts.Remove(A); + } + } + } + protected void BotChangedSim(object sender, SimChangedEventArgs e) + { + TriggerEvent("ChangeSim", new Dictionary() + { + { "oldsimname", e.PreviousSimulator.Name }, + { "newsimname", GetClient().Network.CurrentSim.Name }, + }); + } + + protected string inArgsOrDefault(string arg, Dictionary args, string defaultvalue) + { + if (args.ContainsKey(arg) == true) + { + return args[arg]; + } + return defaultvalue; + } + protected void TriggerEvent(string eventName, Dictionary args) + { + if (MyCustomEvents.ContainsKey(eventName) == false) + { + return; + } + string avataruuid = inArgsOrDefault("avataruuid", args, null); + + List AvPos = GetAvPos(avataruuid); + Vector3 A = GetClient().Self.SimPosition; + List BotPos = new List() { (int)Math.Round(A.X), (int)Math.Round(A.Y), (int)Math.Round(A.Z) }; + Dictionary values = new Dictionary + { + { "alertmessage", inArgsOrDefault("alertmessage", args, "none") }, + { "message", inArgsOrDefault("message", args, "none") }, + { "funds", inArgsOrDefault("funds", args, "-1") }, + { "eventype", eventName }, + { "groupuuid", inArgsOrDefault("groupuuid", args, "none") }, + { "groupname", inArgsOrDefault("groupname", args, "none") }, + { "oldsimname", inArgsOrDefault("oldsimname", args, "none") }, + { "newsimname", inArgsOrDefault("newsimname", args, "none") }, + { "avataruuid", avataruuid }, + { "avatarname", GetAvName(avataruuid) }, + { "avatarparcel", GetAvParcel(avataruuid) }, + { "botsim", GetClient().Network.CurrentSim.Name }, + { "botparcel", GetBotParcel() }, + { "avatarx", AvPos[0].ToString() }, + { "avatary", AvPos[1].ToString() }, + { "avatarz", AvPos[2].ToString() }, + { "avatardistance", AvPos[3].ToString() }, + { "botx", AvPos[0].ToString() }, + { "boty", AvPos[1].ToString() }, + { "botz", AvPos[2].ToString() }, + { "clockhour", DateTime.Now.ToString("HH") }, + { "clockmin", DateTime.Now.ToString("mm") }, + { "dayofweek", ((int)DateTime.Now.DayOfWeek).ToString() } + }; + + foreach (CustomOnEvent E in MyCustomEvents[eventName]) + { + bool canFireEvent = true; + if ((E.Source == "GroupMemberJoin") || (E.Source == "GroupMemberLeave")) + { + if (E.MonitorFlags[0] != values["groupuuid"]) + { + continue; + } + } + else if ((E.Source == "GuestJoins") || (E.Source == "GuestLeaves")) + { + if ( + (E.MonitorFlags[0] != values["botsim"]) || + (E.MonitorFlags[1] != values["botparcel"]) || + (E.MonitorFlags[1] != values["avatarparcel"]) + ) + { + continue; + } + } + int loop = 0; + while(loop < E.WhereChecksLeft.Count) + { + string left = swapvalues(E.WhereChecksLeft[loop],values); + string center = E.WhereChecksCenter[loop]; + string right = swapvalues(E.WhereChecksRight[loop], values); + int leftAsInt = -1; + int rightAsInt = -1; + int.TryParse(left, out leftAsInt); + int.TryParse(right, out rightAsInt); + bool leftAsBool = false; + if(left == "true") leftAsBool = true; + bool rightAsBool = false; + if (right == "true") rightAsBool = true; + bool leftContactinsRight = left.Contains(right); + UUID leftAsUUID = UUID.Zero; + bool leftIsUUID = UUID.TryParse(left, out leftAsUUID); + UUID rightAsUUID = UUID.Zero; + bool rightIsUUID = UUID.TryParse(right, out rightAsUUID); + bool leftIsInGroup = false; + bool leftIsLockedout = IsLockedout(left); + bool leftIsEven = true; + bool leftIsDivs = true; + if (leftAsInt != 0) + { + leftIsEven = ((leftAsInt % 2) == 0); + if(rightAsInt != 0) + { + leftIsDivs = ((leftAsInt % rightAsInt) == 0); + } + } + if ((leftIsUUID == true) && (rightIsUUID == true)) + { + leftIsInGroup = master.DataStoreService.IsGroupMember(rightAsUUID, leftAsUUID); + } + if ((center == "IS") && (left != right)) { canFireEvent = false; } + else if ((center == "NOT") && (left == right)) { canFireEvent = false; } + else if ((center == "IS_UUID") && (leftIsUUID != rightAsBool)) { canFireEvent = false; } + else if ((center == "MISSING") && (leftContactinsRight == true)) { canFireEvent = false; } + else if ((center == "CONTAINS") && (leftContactinsRight == false)) { canFireEvent = false; } + else if ((center == "IN_GROUP") && (leftIsInGroup == false)) { canFireEvent = false; } + else if ((center == "NOT_IN_GROUP") && (leftIsUUID == true) && (rightIsUUID == true) && (leftIsInGroup == true)) { canFireEvent = false; } + else if ((center == "LESSTHAN") && (leftAsInt >= rightAsInt)) { canFireEvent = false; } + else if ((center == "MORETHAN") && (leftAsInt <= rightAsInt)) { canFireEvent = false; } + else if ((center == "LOCKOUT") && (leftIsLockedout != rightAsBool)) { canFireEvent = false; } + else if ((center == "IS_EVEN") && (leftIsEven != rightAsBool)) { canFireEvent = false; } + else if ((center == "DIVISIBLE") && (leftIsDivs == false)) { canFireEvent = false; } + if (canFireEvent == false) + { + break; + } + loop++; + } + if (canFireEvent == false) + { + continue; + } + foreach(string action in E.Actions) + { + string useaction = swapvalues(action, values); + string[] bits = useaction.Split('='); + if(bits.Length == 2) + { + if (bits[0] == "lockout") + { + bits = bits[1].Split("+"); + if(bits.Length == 2) + { + if (int.TryParse(bits[1],out int mins) == true) + { + AddToLockout(bits[0], mins); + } + } + continue; + } + } + master.CommandsService.CommandInterfaceCaller(useaction, false, false, "OnEvents"); + } + } + } + + protected string swapvalues(string input, Dictionary values) + { + foreach(KeyValuePair pair in values) + { + input = input.Replace("["+pair.Key+"]", pair.Value); + } + return input; + } + + protected List GetAvPos(string avataruuid) + { + if (UUID.TryParse(avataruuid, out UUID avUUID) == false) + { + return new List() { -1, -1, -1, -1 }; + } + foreach (Avatar A in GetClient().Network.CurrentSim.ObjectsAvatars.Copy().Values) + { + if (A.ID != avUUID) + { + continue; + } + Vector3 avpos = A.Position; + int dist = (int)Math.Round(Vector3.Distance(avpos, GetClient().Self.SimPosition)); + return new List() { (int)Math.Round(A.Position.X), (int)Math.Round(A.Position.Y), (int)Math.Round(A.Position.Z), dist }; + } + return new List() { -1, -1, -1, -1 }; + } + + protected string GetAvParcel(string avataruuid) + { + if (UUID.TryParse(avataruuid, out UUID avUUID) == false) + { + return "?"; + } + foreach (Avatar A in GetClient().Network.CurrentSim.ObjectsAvatars.Copy().Values) + { + if(A.ID != avUUID) + { + continue; + } + int localid = GetClient().Parcels.GetParcelLocalID(GetClient().Network.CurrentSim, A.Position); + if (GetClient().Network.CurrentSim.Parcels.ContainsKey(localid) == false) + { + return "?"; + } + return GetClient().Network.CurrentSim.Parcels[localid].Name; + } + return "?"; + } + + protected string GetBotParcel() + { + int localid = GetClient().Parcels.GetParcelLocalID(GetClient().Network.CurrentSim, GetClient().Self.SimPosition); + if (GetClient().Network.CurrentSim.Parcels.ContainsKey(localid) == false) + { + return "-"; + } + return GetClient().Network.CurrentSim.Parcels[localid].Name; + } + + string avatarhash = ""; + bool GuestListLoaded = false; + protected List AvatarsOnSim = new List(); + protected void TrackAvatarsOnParcel() + { + long dif = SecondbotHelpers.UnixTimeNow() - lastAvUpdate; + if (dif < 30) + { + // only check for changes every 30 secs + return; + } + lastAvUpdate = SecondbotHelpers.UnixTimeNow(); + string hashstring = ""; + List avs = new List(); + foreach (Avatar A in GetClient().Network.CurrentSim.ObjectsAvatars.Copy().Values) + { + avs.Add(A.ID); + master.DataStoreService.AddAvatar(A.ID, A.Name); + hashstring = hashstring + A.ID.ToString(); + } + string hash = SecondbotHelpers.GetSHA1(hashstring); + if (hash == avatarhash) + { + // no changes + return; + } + avatarhash = hash; + + if (GuestListLoaded == true) + { + List joined = new List(); + List tracked = new List(); + foreach (UUID a in AvatarsOnSim) + { + tracked.Add(a); + } + foreach (UUID a in avs) + { + if (tracked.Contains(a) == true) + { + tracked.Remove(a); + continue; + } + joined.Add(a); + } + if ((tracked.Count > 0) || (joined.Count > 0)) + { + List seen = new List(); + if (tracked.Count > 0) + { + + foreach (UUID a in tracked) + { + if(seen.Contains(a) == true) + { + continue; + } + seen.Add(a); + TriggerEvent("GuestLeaves", new Dictionary() { { "avataruuid", a.ToString() } }); + } + } + if (joined.Count > 0) + { + foreach (UUID a in joined) + { + if (seen.Contains(a) == true) + { + continue; + } + seen.Add(a); + TriggerEvent("GuestJoins", new Dictionary() { { "avataruuid", a.ToString() } }); + } + } + } + } + AvatarsOnSim = avs; + GuestListLoaded = true; + } + + long lastClockEvent = 0; + protected void ClockEvent() + { + long dif = SecondbotHelpers.UnixTimeNow() - lastClockEvent; + if (dif < 60) + { + return; + } + lastClockEvent = SecondbotHelpers.UnixTimeNow(); + TriggerEvent("Clock", new Dictionary()); + } + + long lastAvUpdate = 0; + protected void TimedEvents() + { + TrackAvatarsOnParcel(); + CleanLockouts(); + ClockEvent(); + GroupMembershipUpkeep(); + } + + public override string Status() + { + if (myConfig == null) + { + return "No Config"; + } + else if (myConfig.GetEnabled() == false) + { + return "- Not requested -"; + } + if (botConnected == true) + { + TimedEvents(); + } + if (myConfig.GetHideStatusOutput() == true) + { + return "hidden"; + } + else if (botConnected == false) + { + return "Waiting for bot"; + } + return "Active"; + } + + protected void BotLoggedIn(object o, SimConnectedEventArgs e) + { + GetClient().Network.SimConnected -= BotLoggedIn; + botConnected = true; + LogFormater.Info("OnEvent Service [Active]"); + AttachEvents(); + } + + public override void Start() + { + if (myConfig.GetEnabled() == false) + { + LogFormater.Info("OnEvent Service [- Not requested -]"); + return; + } + Stop(); + running = true; + master.BotClientNoticeEvent += BotClientRestart; + LogFormater.Info("OnEvent Service [Starting]"); + } + + public override void Stop() + { + if (running == true) + { + LogFormater.Info("OnEvent Service [Stopping]"); + } + running = false; + if (myConfig.GetEnabled() == false) + { + return; + } + master.BotClientNoticeEvent -= BotClientRestart; + RemoveEvents(); + } + } + + public class CustomOnEvent + { + public string Source = ""; + public List MonitorFlags = new List(); + public List WhereChecksLeft = new List(); + public List WhereChecksCenter = new List(); + public List WhereChecksRight = new List(); + public List Actions = new List(); + } +}