SecondBot/SecondBotEvents/Services/EventsService.cs
Madpeter 008cb44e24 Use UnsafeRelaxed JSON serialization globally
Replaces all usages of JsonSerializer.Serialize with the UnsafeRelaxed option via a new JsonOptions.UnsafeRelaxed static property. This ensures consistent relaxed escaping for all JSON output, improving compatibility with consumers expecting unescaped JSON. Refactors affected command, service, and static classes to use the new option.
2025-10-05 20:07:42 +01:00

512 lines
18 KiB
C#

using OpenMetaverse;
using SecondBotEvents.Config;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Text.Json;
using System.Linq;
using Swan;
using RestSharp;
using System.Threading.Tasks;
namespace SecondBotEvents.Services
{
public class EventsService : BotServices
{
protected new EventsConfig myConfig;
protected bool botConnected = false;
protected string lastSimName = "";
protected UUID TrackGroupUUID = UUID.Zero;
protected List<UUID> GroupMembers = [];
protected bool GroupLoaded = false;
protected List<UUID> AvatarsOnSim = [];
protected bool GuestListLoaded = false;
protected string lastparcelname = "";
protected UUID OutputAvatar = UUID.Zero;
protected long lastGroupMembershipUpdate = 0;
public void InventoryUpdateEvent(Dictionary<string, string> detail)
{
Task.Run(() =>
{
detail["itemtype"] = detail["itemtype"].ToLower().FirstCharToUpper();
if (InventoryEvents.TryGetValue(detail["itemtype"], out var targets))
{
foreach (string A in targets)
{
string output = JsonSerializer.Serialize(detail, JsonOptions.UnsafeRelaxed);
if (UUID.TryParse(A, out UUID OutputAvatar) == true)
{
if (OutputAvatar != UUID.Zero)
{
GetClient().Self.InstantMessage(OutputAvatar, output);
}
}
else if (A.StartsWith("http") == true)
{
long unixtime = SecondbotHelpers.UnixTimeNow();
string token = SecondbotHelpers.GetSHA1(unixtime.ToString() + "EventService" + GetClient().Self.AgentID + output + master.CommandsService.myConfig.GetSharedSecret());
var client = new RestClient(A);
var request = new RestRequest("Inventory/Update", Method.Post);
request.AddParameter("token", token);
request.AddParameter("unixtime", unixtime.ToString());
request.AddParameter("method", "Inventory");
request.AddParameter("action", "Update");
request.AddParameter("botname", GetClient().Self.Name);
request.AddParameter("event", output);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
client.ExecutePostAsync(request);
}
else if (int.TryParse(A, out int chann) == true)
{
if (chann >= 0)
{
GetClient().Self.Chat(output, chann, ChatType.Normal);
}
}
}
}
});
}
Dictionary<string, List<string>> InventoryEvents = [];
public void AddInventoryEvent(string inventoryType, string outputTarget)
{
if(InventoryEvents.ContainsKey(inventoryType) == false)
{
InventoryEvents.Add(inventoryType, []);
}
if (InventoryEvents[inventoryType].Contains(outputTarget) == false)
{
InventoryEvents[inventoryType].Add(outputTarget);
}
}
public EventsService(EventsSecondBot setMaster) : base(setMaster)
{
myConfig = new EventsConfig(master.fromEnv, master.fromFolder);
if(myConfig.GetEnabled() == false)
{
return;
}
if (UUID.TryParse(myConfig.GetOutputIMuuid(), out OutputAvatar) == false)
{
OutputAvatar = UUID.Zero;
}
if (UUID.TryParse(myConfig.GetGroupMemberEventsGroupUUID(), out TrackGroupUUID) == false)
{
TrackGroupUUID = UUID.Zero;
}
}
public override string Status()
{
if (myConfig == null)
{
return "No Config";
}
else if (myConfig.GetHideStatusOutput() == true)
{
return "hidden";
}
else if (myConfig.GetEnabled() == false)
{
return "- Not requested -";
}
else if (botConnected == false)
{
return "Waiting for bot";
}
TimedEvents();
return "Active";
}
protected void BotClientRestart(object o, BotClientNotice e)
{
if (e.isStart == false)
{
return;
}
LogFormater.Info("Events service [Attached to new client]");
GetClient().Network.LoggedOut += BotLoggedOut;
GetClient().Network.SimConnected += BotLoggedIn;
}
protected void BotLoggedOut(object o, LoggedOutEventArgs e)
{
GetClient().Network.SimConnected += BotLoggedIn;
botConnected = false;
LogFormater.Info("Events service [Bot commands disabled]");
removeEvents();
}
protected void BotLoggedIn(object o, SimConnectedEventArgs e)
{
GetClient().Network.SimConnected -= BotLoggedIn;
botConnected = true;
// attach events
GetClient().Self.MoneyBalanceReply += BotMoneyEvent;
GetClient().Network.SimChanged += BotChangedSim;
GetClient().Groups.GroupMembersReply += BotGroupMembers;
GetClient().Self.AlertMessage += BotAlertMessage;
}
protected long lastAvUpdate = 0;
protected string avatarhash = "";
protected void TrackAvatarsOnParcel()
{
if ((myConfig.GetGuestTrackingSimname() != GetClient().Network.CurrentSim.Name) || (myConfig.GetGuestTrackingParcelname() != lastparcelname))
{
AvatarsOnSim = [];
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<UUID> avs = [];
foreach(Avatar A in GetClient().Network.CurrentSim.ObjectsAvatars.ToDictionary(k => k.Key, v => v.Value).Values)
{
avs.Add(A.ID);
hashstring = hashstring+A.ID.ToString();
}
string hash = SecondbotHelpers.GetSHA1(hashstring);
if (hash == avatarhash)
{
return;
}
avatarhash = hash;
if (GuestListLoaded == true)
{
List<UUID> joined = [];
List<UUID> tracked = [.. AvatarsOnSim];
foreach (UUID a in avs)
{
if (tracked.Contains(a) == true)
{
tracked.Remove(a);
continue;
}
joined.Add(a);
}
if ((tracked.Count > 0) || (joined.Count > 0))
{
if ((tracked.Count > 0) && (myConfig.GetGuestLeavesArea() == true))
{
foreach (UUID a in tracked)
{
GuestTrackingEvent(a, "exit");
}
}
if ((joined.Count > 0) && (myConfig.GetGuestEntersArea() == true))
{
foreach (UUID a in joined)
{
GuestTrackingEvent(a, "enter");
}
}
}
}
AvatarsOnSim = avs;
GuestListLoaded = true;
}
protected void TimedEvents()
{
int localid = GetClient().Parcels.GetParcelLocalID(GetClient().Network.CurrentSim, GetClient().Self.SimPosition);
if (GetClient().Network.CurrentSim.Parcels.ContainsKey(localid) == false)
{
GetClient().Parcels.GetParcelLocalID(GetClient().Network.CurrentSim, GetClient().Self.SimPosition);
return;
}
if(lastparcelname != GetClient().Network.CurrentSim.Parcels[localid].Name)
{
BotChangedParcel(GetClient().Network.CurrentSim.Parcels[localid].Name);
}
TrackAvatarsOnParcel();
if(TrackGroupUUID != UUID.Zero)
{
long dif = SecondbotHelpers.UnixTimeNow() - lastGroupMembershipUpdate;
if (dif > 120)
{
lastGroupMembershipUpdate = SecondbotHelpers.UnixTimeNow();
GetClient().Groups.RequestGroupMembers(TrackGroupUUID);
}
}
}
protected void removeEvents()
{
botConnected = false;
if (GetClient() != null)
{
GetClient().Self.MoneyBalanceReply -= BotMoneyEvent;
GetClient().Network.SimChanged -= BotChangedSim;
GetClient().Groups.GroupMembersReply -= BotGroupMembers;
GetClient().Self.AlertMessage -= BotAlertMessage;
}
}
protected void PushEvent(string eventname, Dictionary<string,string> args)
{
OnEvent e = new(
GetClient().Self.AgentID.ToString(),
eventname,
args,
myConfig.GetOutputSecret()
);
string output = JsonSerializer.Serialize(e, JsonOptions.UnsafeRelaxed);
if(myConfig.GetOutputChannel() >= 0)
{
GetClient().Self.Chat(output, myConfig.GetOutputChannel(), ChatType.Normal);
}
if(OutputAvatar != UUID.Zero)
{
GetClient().Self.InstantMessage(OutputAvatar, output);
}
if(SecondbotHelpers.IsEmpty(myConfig.GetOutputHttpURL()) == false)
{
if(myConfig.GetOutputHttpURL().StartsWith("http") == false)
{
return;
}
long unixtime = SecondbotHelpers.UnixTimeNow();
string token = SecondbotHelpers.GetSHA1(unixtime.ToString() + "EventService" + GetClient().Self.AgentID + output + master.CommandsService.myConfig.GetSharedSecret());
var client = new RestClient(myConfig.GetOutputHttpURL());
var request = new RestRequest("Event/Service", Method.Post);
request.AddParameter("token", token);
request.AddParameter("unixtime", unixtime.ToString());
request.AddParameter("method", "Dialog");
request.AddParameter("action", "Relay");
request.AddParameter("botname", GetClient().Self.Name);
request.AddParameter("event", output);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
client.ExecutePostAsync(request);
}
}
protected void BotAlertMessage(object senderm, AlertMessageEventArgs e)
{
if(myConfig.GetSimAlertMessage() == false)
{
return;
}
Dictionary<string, string> args = new()
{
{ "Message", e.Message },
{ "NotificationId", e.NotificationId }
};
PushEvent("AlertMessage", args);
}
protected void BotChangedParcel(string newparcelname)
{
if (myConfig.GetChangeParcel() == false)
{
return;
}
Dictionary<string, string> args = new()
{
{ "oldparcel", lastparcelname },
{ "newparcel", newparcelname }
};
lastparcelname = newparcelname;
PushEvent("ChangedParcel", args);
}
protected void BotStatusMessage(object sender, SystemStatusMessage e)
{
if((e.changed == false) || (myConfig.GetStatusMessage() == false))
{
return;
}
Dictionary<string, string> args = new()
{
{ "Message", e.message }
};
PushEvent("StatusMessage", args);
}
protected void BotGroupMembers(object sender, GroupMembersReplyEventArgs e)
{
if(e.GroupID != TrackGroupUUID)
{
return;
}
if(GroupLoaded == true)
{
List<UUID> joined = [];
List<UUID> tracked = [.. GroupMembers];
master.DataStoreService.GetAvatarNames(tracked);
foreach (UUID a in e.Members.Keys)
{
if(tracked.Contains(a) == true)
{
tracked.Remove(a);
continue;
}
joined.Add(a);
}
if((tracked.Count > 0) && (myConfig.GetGroupMemberLeaves() == true))
{
foreach(UUID a in tracked)
{
GroupMembershipEvent(a, "leave");
}
}
if ((joined.Count > 0) && (myConfig.GetGroupMemberJoins() == true))
{
foreach (UUID a in joined)
{
GroupMembershipEvent(a, "join");
}
}
}
GroupMembers = [.. e.Members.Keys];
GroupLoaded = true;
}
protected void GroupMembershipEvent(UUID avatar, string action)
{
Dictionary<string, string> args = new()
{
{ "uuid", avatar.ToString() },
{ "name", master.DataStoreService.GetAvatarName(avatar) },
{ "action", action }
};
PushEvent("GroupMembership", args);
}
protected void GuestTrackingEvent(UUID avatar, string action)
{
Dictionary<string, string> args = new()
{
{ "uuid", avatar.ToString() },
{ "name", master.DataStoreService.GetAvatarName(avatar) },
{ "action", action }
};
PushEvent("GuestTracking", args);
}
protected void BotChangedSim(object sender, SimChangedEventArgs e)
{
if(myConfig.GetChangeSim() == false)
{
return;
}
Dictionary<string, string> args = new()
{
{ "lastsim", lastSimName },
{ "newsim", GetClient().Network.CurrentSim.Name }
};
PushEvent("ChangedSim", args);
lastSimName = e.PreviousSimulator.Name;
}
protected void BotMoneyEvent(object sender, MoneyBalanceReplyEventArgs e)
{
if(myConfig.GetMoneyEvent() == false)
{
return;
}
Dictionary<string, string> args = new()
{
{ "fromuuid", e.TransactionInfo.SourceID.ToString() },
{ "touuid", e.TransactionInfo.DestID.ToString() },
{ "amount", e.TransactionInfo.Amount.ToString() },
{ "balance", e.Balance.ToString() },
{ "fromname", master.DataStoreService.GetAvatarName(e.TransactionInfo.SourceID) },
{ "toname", master.DataStoreService.GetAvatarName(e.TransactionInfo.DestID) },
{ "transactionid", e.TransactionID.ToString() }
};
PushEvent("MoneyEvent", args);
}
public override void Start(bool updateEnabled = false, bool setEnabledTo = false)
{
if (updateEnabled)
{
myConfig.setEnabled(setEnabledTo);
}
if (myConfig.GetEnabled() == false)
{
LogFormater.Info("Events service [Disabled]");
return;
}
Stop();
running = true;
master.BotClientNoticeEvent += BotClientRestart;
master.SystemStatusMessagesEvent += BotStatusMessage;
}
public override void Stop()
{
if (myConfig.GetEnabled() == false)
{
return;
}
running = false;
master.BotClientNoticeEvent -= BotClientRestart;
master.SystemStatusMessagesEvent -= BotStatusMessage;
removeEvents();
}
}
public class OnEvent
{
public string EventName = "";
public Dictionary<string, string> Data = [];
public string Hash = "";
public long Unixtime = 0;
public string BotUUID = "";
public OnEvent(string eventBotUUID, string setEventName, Dictionary<string,string> eventData, string Secret)
{
BotUUID = eventBotUUID;
EventName = setEventName;
Data = eventData;
Unixtime = SecondbotHelpers.UnixTimeNow();
SignEvent(Secret);
}
public string getJson()
{
return "";
}
protected void SignEvent(string Secret)
{
string raw = BotUUID+ EventName;
foreach(string A in Data.Values)
{
raw = raw + A;
}
raw = raw + Unixtime.ToString() + Secret;
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;
}
}